看到文章里的同学留言说到unique_ptr,这两天看了一下cplusplus提供的reference才知道这个东西是c++11的新特性,对c++11的新特性不是很了解,花时间了解了下unique_ptr,之前有写过auto_ptr的分析,这里就和auto_ptr对比下来看。
- unique_ptr的构造函数与auto_ptr一样,采用explicit声明,防止复制/拷贝时不必要的类型转换,在定义对象时必须显示调用初始化式,不能使用赋值操作符进行隐式转换。
- unique_ptr同样要重复释放指针的可能:
1 int _tmain(int argc, _TCHAR* argv[]) 2 { 3 int *p = new int(12); 4 unique_ptr<int> up(p); 5 unique_ptr<int> up2(p); 6 7 return 0; 8 }
up析构时,已经将p指针delete,而up2析构会重复delete,出现未定义操作。auto_ptr析构函数只是单纯的delete掉raw指针,而unique_ptr则可以定制自己的deleter,来指定unique_ptr析构时需要做哪些工作。默认情况下unique_ptr使用的deleter如下:
1 void operator()(_Ty *_Ptr) const _NOEXCEPT 2 { // delete a pointer 3 static_assert(0 < sizeof (_Ty), 4 "can't delete an incomplete type"); 5 delete _Ptr; 6 }
可以看出这是一个函数对象,功能很简单,delete raw指针。开发人员可以定制自己的deleter,一个很简单的例子:
1 struct my_deleter 2 { 3 void operator()(int *p) 4 { 5 cout<<"delete point, value = "<<*p<<endl; 6 delete p; 7 } 8 }; 9 int _tmain(int argc, _TCHAR* argv[]) 10 { 11 unique_ptr<int, my_deleter> up(new int(12), my_deleter()); 12 13 return 0; 14 }
- auto_ptr只能托管单独的指针,而不能用于堆上动态分配的数组。而unique_ptr则可以用于数组:
1 struct Item 2 { 3 Item(){cout<<"Construct "<<endl;} 4 ~Item() {cout<<"Destruct"<<endl;} 5 }; 6 int _tmain(int argc, _TCHAR* argv[]) 7 { 8 Item *par = new Item[5]; 9 unique_ptr<Item[]> uparr(par); 10 11 return 0; 12 }
在vs2012中运行上面的代码结果如下,根据输出中五个"Destruct"可以看出uparr对象在析构时调用了delete[]:
- auto_ptr对象可以进行拷贝和赋值,之后源对象不再拥有raw指针的所有权,转而交给新对象托管。unique_ptr对象禁止拷贝和赋值运算,在vs2012源码中,将拷贝构造函数和赋值操作符声明为private,并未定义,所以如下代码中,line5,6两行均会导致编译报错:
1 int _tmain(int argc, _TCHAR* argv[]) 2 { 3 int *p = new int(12); 4 unique_ptr<int> up(p); 5 unique_ptr<int> up_copy(up); 6 unique_ptr<int> up_assign; 7 up_assign = up; 8 9 return 0; 10 }
因为此特性,猜想unique_ptr对象无法很好地与STL容器一起使用(PS:最初我以为将拷贝构造和赋值操作符私有化的类,声明一个容器持有这种类型,这样的代码编译就会报错,编码后才知道编译不会报错,调用push_back等需要拷贝或赋值的操作时,才会报错),下面代码中line5将会报错:
1 int _tmain(int argc, _TCHAR* argv[]) 2 { 3 unique_ptr<int> up(new int(12)); 4 vector<unique_ptr<int> > vup; 5 vup.push_back(up); 6 7 return 0; 8 }
之后了解到c++11中引入的move语义使得unique_ptr可以存放到容器中,参考这篇文章http://www.th7.cn/Program/cp/201408/267890.shtml。使用move就表示放弃对该对象的所有权,但并不对raw指针进行释放,举个例子:
1 int _tmain(int argc, _TCHAR* argv[]) 2 { 3 vector<unique_ptr<int, my_deleter> > vup; 4 { 5 cout<<"scope begin######################"<<endl; 6 unique_ptr<int, my_deleter> up(new int(12), my_deleter()); 7 vup.push_back(move(up)); 8 if(up.get() == NULL) 9 cout<<"up points NULL"<<endl; 10 cout<<"scope end######################"<<endl; 11 } 12 cout<<"outer######################"<<endl; 13 14 return 0; 15 }
根据下面的输出可以验证,在局部对象up经过move函数调用后,失去了对raw指针的所有权(line9),并未释放raw指针,之后如果误用了up对象,会导致undefine行为:
这里需要注意的是,此处move函数调用了unique_ptr的move语意拷贝构造函数(不知道c++11是否这么称呼……),注意形参类型为unique_ptr&&
1 unique_ptr(unique_ptr&& _Right) _NOEXCEPT 2 : _Mybase(_Right.release(), 3 _STD forward<_Dx>(_Right.get_deleter())) 4 { // construct by moving _Right 5 }
这里确实没有调用deleter,进行raw指针的释放。forward函数同样是c++11里的语法,表示”接受一个参数,然后返回该参数本来所对应的类型的引用”。
目前对unique_ptr的了解就到这里,不算太深,可以看出unique_str的功能比auto_ptr更为强大,它支持托管堆上分配的数组,支持定制deleter,并且可以通过move语意使unique_ptr对象与容器兼容,但仍然有一些不足,比如重复释放,使用move语意之后源对象失去了对raw指针的管理权,再次使用会出现undefine行为。要避免这些情况,除了使用时要注意之外,最好的办法还是使用带有引用计数功能的智能指针。