本文介紹c++裡面的四個智慧指標: auto_ptr, shared_ptr, weak_ptr, unique_ptr 其中後三個是c++11支援,並且第一個已經被c++11棄用。
為什麼要使用智慧指標:我們知道c++的記憶體管理是讓很多人頭疼的事,當我們寫一個new語句時,一般就會立即把delete語句直接也寫了,但是我們不能避免程式還未執行到delete時就跳轉了或者在函式中沒有執行到最後的delete語句就返回了,如果我們不在每一個可能跳轉或者返回的語句前釋放資源,就會造成記憶體洩露。使用智慧指標可以很大程度上的避免這個問題,因為智慧指標就是一個類,當超出了類的作用域是,類會自動呼叫解構函式,解構函式會自動釋放資源。下面我們逐個介紹。
auto_ptr (官方文件)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | class Test { public : Test(string s) { str = s; cout<< "Test creat\n" ; } ~Test() { cout<< "Test delete:" <<str<<endl; } string& getStr() { return str; } void setStr(string s) { str = s; } void print() { cout<<str<<endl; } private : string str; }; int main() { auto_ptr<Test> ptest( new Test( "123" )); ptest->setStr( "hello " ); ptest->print(); ptest.get()->print(); ptest->getStr() += "world !" ; (*ptest).print(); ptest.reset( new Test( "123" )); ptest->print(); return 0; } |
執行結果如下
如上面的程式碼:智慧指標可以像類的原始指標一樣訪問類的public成員,成員函式get()返回一個原始的指標,成員函式reset()重新繫結指向的物件,而原來的物件則會被釋放。注意我們訪問auto_ptr的成員函式時用的是“.”,訪問指向物件的成員時用的是“->”。我們也可用宣告一個空智慧指標auto_ptr<Test>ptest();
當我們對智慧指標進行賦值時,如ptest2 = ptest,ptest2會接管ptest原來的記憶體管理權,ptest會變為空指標,如果ptest2原來不為空,則它會釋放原來的資源,基於這個原因,應該避免把auto_ptr放到容器中,因為演算法對容器操作時,很難避免STL內部對容器實現了賦值傳遞操作,這樣會使容器中很多元素被置為NULL。判斷一個智慧指標是否為空不能使用if(ptest == NULL),應該使用if(ptest.get() == NULL),如下程式碼 本文地址
1 2 3 4 5 6 7 8 9 | int main() { auto_ptr<Test> ptest( new Test( "123" )); auto_ptr<Test> ptest2( new Test( "456" )); ptest2 = ptest; ptest2->print(); if (ptest.get() == NULL)cout<< "ptest = NULL\n" ; return 0; } |
還有一個值得我們注意的成員函式是release,這個函式只是把智慧指標賦值為空,但是它原來指向的記憶體並沒有被釋放,相當於它只是釋放了對資源的所有權,從下面的程式碼執行結果可以看出,解構函式沒有被呼叫。
1 2 3 4 5 6 | int main() { auto_ptr<Test> ptest( new Test( "123" )); ptest.release(); return 0; } |
那麼當我們想要在中途釋放資源,而不是等到智慧指標被析構時才釋放,我們可以使用ptest.reset(); 語句。
unique_ptr (官方文件)
unique_ptr,是用於取代c++98的auto_ptr的產物,在c++98的時候還沒有移動語義(move semantics)的支援,因此對於auto_ptr的控制權轉移的實現沒有核心元素的支援,但是還是實現了auto_ptr的移動語義,這樣帶來的一些問題是拷貝建構函式和複製操作過載函式不夠完美,具體體現就是把auto_ptr作為函式引數,傳進去的時候控制權轉移,轉移到函式引數,當函式返回的時候並沒有一個控制權移交的過程,所以過了函式呼叫則原先的auto_ptr已經失效了.在c++11當中有了移動語義,使用move()把unique_ptr傳入函式,這樣你就知道原先的unique_ptr已經失效了.移動語義本身就說明了這樣的問題,比較坑爹的是標準描述是說對於move之後使用原來的內容是未定義行為,並非丟擲異常,所以還是要靠人肉遵守遊戲規則.再一個,auto_ptr不支援傳入deleter,所以只能支援單物件(delete object),而unique_ptr對陣列型別有偏特化過載,並且還做了相應的優化,比如用[]訪問相應元素等.
unique_ptr 是一個獨享所有權的智慧指標,它提供了嚴格意義上的所有權,包括:
1、擁有它指向的物件
2、無法進行復制構造,無法進行復制賦值操作。即無法使兩個unique_ptr指向同一個物件。但是可以進行移動構造和移動賦值操作
3、儲存指向某個物件的指標,當它本身被刪除釋放的時候,會使用給定的刪除器釋放它指向的物件
unique_ptr 可以實現如下功能:
1、為動態申請的記憶體提供異常安全
2、講動態申請的記憶體所有權傳遞給某函式
3、從某個函式返回動態申請記憶體的所有權
4、在容器中儲存指標
5、auto_ptr 應該具有的功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | unique_ptr<Test> fun() { return unique_ptr<Test>( new Test( "789" )); } int main() { unique_ptr<Test> ptest( new Test( "123" )); unique_ptr<Test> ptest2( new Test( "456" )); ptest->print(); ptest2 = std::move(ptest); //不能直接ptest2 = ptest if (ptest == NULL)cout<< "ptest = NULL\n" ; Test* p = ptest2.release(); p->print(); ptest.reset(p); ptest->print(); ptest2 = fun(); //這裡可以用=,因為使用了移動建構函式 ptest2->print(); return 0; } |
unique_ptr 和 auto_ptr用法很相似,不過不能使用兩個智慧指標賦值操作,應該使用std::move; 而且它可以直接用if(ptest == NULL)來判斷是否空指標;release、get、reset等用法也和auto_ptr一致,使用函式的返回值賦值時,可以直接使用=, 這裡使用c++11 的移動語義特性。另外注意的是當把它當做引數傳遞給函式時(使用值傳遞,應用傳遞時不用這樣),傳實參時也要使用std::move,比如foo(std::move(ptest))。它還增加了一個成員函式swap用於交換兩個智慧指標的值
share_ptr (官方文件)
從名字share就可以看出了資源可以被多個指標共享,它使用計數機制來表明資源被幾個指標共享。可以通過成員函式use_count()來檢視資源的所有者個數。出了可以通過new來構造,還可以通過傳入auto_ptr, unique_ptr,weak_ptr來構造。當我們呼叫release()時,當前指標會釋放資源所有權,計數減一。當計數等於0時,資源會被釋放。具體的成員函式解釋可以參考 here
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | int main() { shared_ptr<Test> ptest( new Test( "123" )); shared_ptr<Test> ptest2( new Test( "456" )); cout<<ptest2->getStr()<<endl; cout<<ptest2.use_count()<<endl; ptest = ptest2; //"456"引用次數加1,“123”銷燬 ptest->print(); cout<<ptest2.use_count()<<endl; //2 cout<<ptest.use_count()<<endl; //2 ptest.reset(); ptest2.reset(); //此時“456”銷燬 cout<< "done !\n" ; return 0; } |
weak_ptr(官方文件)
weak_ptr是用來解決shared_ptr相互引用時的死鎖問題,如果說兩個shared_ptr相互引用,那麼這兩個指標的引用計數永遠不可能下降為0,資源永遠不會釋放。它是對物件的一種弱引用,不會增加物件的引用計數,和shared_ptr之間可以相互轉化,shared_ptr可以直接賦值給它,它可以通過呼叫lock函式來獲得shared_ptr。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 | class B; class A { public : shared_ptr<B> pb_; ~A() { cout<< "A delete\n" ; } }; class B { public : shared_ptr<A> pa_; ~B() { cout<< "B delete\n" ; } }; void fun() { shared_ptr<B> pb( new B()); shared_ptr<A> pa( new A()); pb->pa_ = pa; pa->pb_ = pb; cout<<pb.use_count()<<endl; cout<<pa.use_count()<<endl; } int main() { fun(); return 0; } |
可以看到fun函式中pa ,pb之間互相引用,兩個資源的引用計數為2,當要跳出函式時,智慧指標pa,pb析構時兩個資源引用計數會減一,但是兩者引用計數還是為1,導致跳出函式時資源沒有被釋放(A B的解構函式沒有被呼叫),如果把其中一個改為weak_ptr就可以了,我們把類A裡面的shared_ptr<B> pb_; 改為weak_ptr<B> pb_; 執行結果如下,這樣的話,資源B的引用開始就只有1,當pb析構時,B的計數變為0,B得到釋放,B釋放的同時也會使A的計數減一,同時pa析構時使A的計數減一,那麼A的計數為0,A得到釋放。
注意的是我們不能通過weak_ptr直接訪問物件的方法,比如B物件中有一個方法print(),我們不能這樣訪問,pa->pb_->print(); 英文pb_是一個weak_ptr,應該先把它轉化為shared_ptr,如:shared_ptr<B> p = pa->pb_.lock(); p->print();
參考資料
胡健:http://www.cnblogs.com/hujian/archive/2012/12/10/2810776.html
胡健:http://www.cnblogs.com/hujian/archive/2012/12/10/2810754.html
胡健:http://www.cnblogs.com/hujian/archive/2012/12/10/2810785.html
天方:http://www.cnblogs.com/TianFang/archive/2008/09/20/1294590.html
gaa_ra:http://blog.csdn.net/gaa_ra/article/details/7841204
cplusplus:http://www.cplusplus.com/
【版權宣告】轉載請註明出處:http://www.cnblogs.com/TenosDoIt/p/3456704.html