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行為。要避免這些情況,除了使用時要注意之外,最好的辦法還是使用帶有引用計數功能的智能指針。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM