1. 程式人生 > >stl中auto_ptr,unique_ptr,shared_ptr,weak_ptr四種智慧指標使用總結

stl中auto_ptr,unique_ptr,shared_ptr,weak_ptr四種智慧指標使用總結

在一次面試過程中被問到了stl中的四種智慧指標的用法
由於經驗不足,我只知道auto_ptr和shared_ptr,然後還說了一個弱...    然後面試官就提示是weak_ptr,之後他又主動說出了unique_ptr
我也只對auto_ptr和shared_ptr做了一下使用場景。
現在回想起來,對這次面試還是很不滿意,對自己的知識面也是很不滿意,最近工作不是很忙,專門來總結一下這四種智慧指標
以免以後重蹈覆轍,也算是一個學習筆記吧。


1. auto_ptr
auto_ptr主要是用來解決資源自動釋放的問題,比如如下程式碼:
void Function()
{
Obj*p = new Obj(20);
...
if (error occor)
throw ... 或者 retrun;
delete p;
}
在函式遇到錯誤之後,一般會拋異常,或者返回,但是這時很可能遺漏之前申請的資源,及時是很有經驗的程式設計師也有可能出現這種錯誤,
而使用auto_ptr會在自己的析夠函式中進行資源釋放。也就是所說的RAII
使用auto_ptr程式碼如下
void Function()
{
auto_ptr<Obj> ptr( new Obj(20) );
...
if (error occur)
throw exception...
}
這樣無論函式是否發生異常,在何處返回,資源都會自動釋放。
需要提一下的是這是一個被c++11標準廢棄的一個智慧指標,為什麼會被廢棄,先看一下下面的程式碼:
auto_ptr<Obj> ptr1( new Obj() );
ptr1->FuncA();
auto_ptr<Obj> ptr2 = ptr1;
ptr2->FuncA();
ptr1->FuncA();  // 這句話會異常
為什麼在把ptr1複製給ptr2之後ptr1再使用就異常了呢?
這也正是他被拋棄的主要原因。
因為auto_ptr複製建構函式中把真是引用的記憶體指標進行的轉移,也就是從ptr1轉移給了ptr2,此時,ptr2引用了Obj記憶體地址,而ptr1引用的記憶體地址為空,、
此時再使用ptr1就異常了。


2. unique_ptr
unique_ptr可以看成是auto_ptr的替代品。因為他對物件的所有權比較專一,所以才叫unique 大笑
i) 無法進行復制構造和賦值操作
auto_ptr與unique_ptr的對比:
auto_ptr<Obj> ap(new Obj() );
auto_ptr<Obj> one (ap) ; // ok
auto_ptr<Obj> two = one; //ok


unique_ptr<Obj> ap(new Obj() );
unique_ptr<Obj> one (ap) ; // 會出錯
unique_ptr<Obj> two = one; //會出錯
也就是說unique_ptr對物件的引用比較專一,不允許隨隨便便的進行轉移
ii)  可以進行移動構造和移動賦值操作
unique_ptr<Obj> GetObj()
{
unique<Obj> ptr( new Obj() );
return ptr;
}
unique<Obj> ptr = GetObj();
上面的程式碼可以順利執行!
什麼是移動構造和移動賦值呢? 這得益於C++11的標準規範。這個知識點可以參考參考網址中的第三個。
那麼如果萬一我就是需要把一個unique_ptr智慧指標賦值給另外一個怎麼辦呢?
可以使用移動函式!如下:
unique<Obj> ptr1( new Obj() );
unique<Obj> ptr2( std::move(ptr1) );
這個效果和auto_ptr直接賦值是一樣的,就是ptr1不再擁有Obj物件了,所以ptr1不能再用來操作記憶體中的Obj物件,因為這個是手動操作的,
所以程式設計師自己也會更加小心。


3. shared_ptr
auto_ptr和unique_ptr都只能一個智慧指標引用物件,而shared_ptr則是可以多個智慧指標同時擁有一個物件。
shared_ptr實現方式就是使用引用計數。這一技術在COM中是用來管理COM物件生命週期的一個方式。
引用計數的原理是,多個智慧指標同時引用一個物件,每當引用一次,引用計數加一,每當智慧指標銷燬了,引用計數就減一,
當引用計數減少到0的時候就釋放引用的物件。這種引用計數的增減發生在智慧指標的建構函式,複製建構函式,賦值操作符,解構函式中。
這種方式使得多個智慧指標同時對所引用的物件有擁有權,同時在引用計數減到0之後也會自動釋放記憶體,也實現了auto_ptr和unique_ptr的資源釋放的功能。


void Function()
 {
shared_ptr<Obj> ptr1( new Obj() ); // 引用計數為1
{
shared_ptr<Obj> ptr2( ptr1 ); // 引用計數為2
{
shared_ptr<Obj> ptr3 = ptr2; // 引用計數為3
int e = 0
}
//引用計數為2
}
//引用計數為1
}
//函式返回之後引用計數為0,new 出來的Obj記憶體已經釋放了


由於shared_ptr支援複製構造,所以他可以作為標準庫容器中的元素
vector<shared_ptr<Obj>> vect;
for (int i = 0; i < 10; ++i)
{
vect.push_back( shared_ptr<Obj>( new Obj() ) );
}
vector<shared_ptr<Obj>> vect2 = vect;
這些操作是auto_ptr和unique_ptr不能實現的。
注意,智慧指標預設使用delete來釋放資源,如果資源是FILE*怎麼辦?釋放的時候就需要用fclose了。
如何實現呢? 
shared_ptr建構函式可以傳遞一個刪除器。
FILE* pStm = fopen(...);
shared_ptr<FILE> fileRes(pStm, &fclose);
或者使用一個仿函式
class FileCloser { 
     public: 


        void operator()(FILE* file) { 


         std::cout << "The FileCloser has been called with a FILE*, " 


           "which will now be closed.\n"; 


         if (file!=0)  


           fclose(file); 


       } 


     }; 
shared_ptr<FILE> fileRes(pStm, FileCloser);
關於這點看一參考這篇文章: http://www.cnblogs.com/learn-my-life/p/3817151.html




4. weak_ptr
shared_ptr是一種強引用的關係,智慧指標直接引用物件。那麼這個會程式碼一個隱含的問題,就是迴圈引用,從而造成記憶體洩漏,即便是java語言有自己的
垃圾回收器,對這種記憶體洩漏也沒有辦法,所以迴圈引用對java程式設計師來說也是一個很值得注意的問題。首先來看一個迴圈引用的例子。


class Parent
{
public:
    shared_ptr<Child> child;
};




class Child
{
public:
    shared_ptr<Parent> parent;
};


void Function()
{
shared_ptr<Parent> pA(new Parent);
shared_ptr<Child> pB(new Child);
pA->child = pB;
pB->parent = pA;
}
現在來分析一下Function函式的執行過程:
1. 第一條語句使得pA引用了Parent一個指標,Parent引用計數為1
2. 第二條語句使得pB引用了Child一個指標,Child引用計數為1
3. 第三條語句,呼叫了shared_ptr<Child>類的賦值操作符,使得Child引用計數變為2
4. 第四條語句,呼叫了shared_ptr<Parent>類的賦值操作符,使得Parent引用計數變為2
5. 函式返回之前呼叫了shared_ptr<Parent>和shared_ptr<Child>類的析夠函式,使得Child引用計數變為1,Parent引用計數變為1
看!函式執行完之後new出來的Parent和Child並沒有釋放,所以出現了記憶體洩漏。
出現洩漏的原因就是pA和pB相互引用了,導致兩者所引用物件的引用計數不能減少到0,造成洩漏。
如果把第三條語句或者第四條語句任意刪除一個,就不會有洩漏了。
這就是強引用所帶來的問題。
weak_ptr從字面意思上可以看出是一個弱指標,不是說明這個指標的能力比較弱,而是說他對他所引用的物件的所有權比較弱,
說得更直接一點兒就是他並不擁有所引用物件的所有權,而且他還不能直接使用他所引用的物件。


在stl中,weak_ptr是和shared_ptr配合使用的,在實現shared_ptr的時候也就考慮了weak_ptr的因素。
weak_ptr是shared_ptr的觀察者,它不會干擾shared_ptr所共享物件的所有權,
當一個weak_ptr所觀察的shared_ptr要釋放它的資源時,它會把相關的weak_ptr的指標設定為空,防止weak_ptr持有懸空的指標。
注意:weak_ptr並不擁有資源的所有權,所以不能直接使用資源。
可以從一個weak_ptr構造一個shared_ptr以取得共享資源的所有權。
void Function()
{
 shared_ptr<int> sp( new Obj() );
 assert(sp.use_count() == 1);
 weak_ptr<int> wp(sp); //從shared_ptr建立weak_ptr
 assert(wp.use_count() == 1);
 if (!wp.expired())//判斷weak_ptr觀察的物件是否失效
 {
  shared_ptr<int> sp2 = wp.lock();//獲得一個shared_ptr
  *sp2 = 100;
  assert(wp.use_count() == 2);
 }
 assert(wp.use_count() == 1);
 return 0;
}
weak_ptr並沒有過載-> 和 * 操作符,所以我們不能通過他來直接使用資源,我們可以通過lock來獲得一個shared_ptr物件
來對資源進行使用,如果引用的資源已經釋放,lock()函式將返回一個儲存空指標的shared_ptr。 expired函式用來判斷資源是否失效。
使用weak_ptr並不會增加資源的引用計數。所以對資源的引用是弱引用,利用這個特性可以解決前面所說的迴圈依賴問題。


class Parent
{
public:
    weak_ptr<Child> child;
};




class Child
{
public:
    weak_ptr<Parent> parent;
};


void Function()
{
shared_ptr<Parent> pA(new Parent);
shared_ptr<Child> pB(new Child);
pA->child = pB;
pB->parent = pA;
}


這個時候第三和第四條語句的執行並沒有增加引用計數,從而在函式執行完成只有能自動釋放記憶體。
從上面的分析可以看出,weak_ptr是一種輔助shared_ptr的一種智慧指標,一般不單獨使用,而是結合
shared_ptr一起使用。


總結:
1. 儘量使用unique_ptr而不要使用auto_ptr
2. 一般來說shared_ptr能夠滿足我們大部分的需求
3. weak_ptr可以避免遞迴的依賴關係


參考網址:
1. http://www.jellythink.com/archives/684
2. http://www.jellythink.com/archives/673
3. http://www.cnblogs.com/TianFang/archive/2013/01/26/2878356.html (c++右值引用相關知識點)
4. http://www.cnblogs.com/learn-my-life/p/3817151.html
5. http://blog.csdn.net/hp_truth/article/details/40511617
6. http://www.cppblog.com/deane/archive/2010/02/25/108428.html(c++臨時物件相關知識點)
7. http://blog.csdn.net/mmzsyx/article/details/8090849
8. http://www.cnblogs.com/learn-my-life/p/3817279.html