unique_ptr简谈

  看到文章里的同学留言说到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行为。要避免这些情况,除了使用时要注意之外,最好的办法还是使用带有引用计数功能的智能指针。

你可能感兴趣的:(unique)