C++ 智能指針

C++ 智能指針

這裏介紹c++裏面的四個智能指針: auto_ptr, shared_ptr, weak_ptr([wiːk]), unique_ptr([juː’niːk]) 其中後三個是c++11支持,並且第一個已經被c++11棄用。

爲什麼要使用智能指針:我們知道c++的內存管理是讓很多人頭疼的事,當我們寫一個new語句時,一般就會立即把delete語句直接也寫了,但是我們不能避免程序還未執行到delete時就跳轉了或者在函數中沒有執行到最後的delete語句就返回了,如果我們不在每一個可能跳轉或者返回的語句前釋放資源,就會造成內存泄露。使用智能指針可以很大程度上的避免這個問題,因爲智能指針就是一個類,當超出了類的作用域是,類會自動調用析構函數,析構函數會自動釋放資源。下面我們逐個介紹。

auto_ptr

智能指針可以像類的原始指針一樣訪問類的public成員,成員函數get()返回一個原始的指針,成員函數reset()重新綁定指向的對象,而原來的對象則會被釋放
注意我們訪問auto_ptr的成員函數時用的是“.”,訪問指向對象的成員時用的是“->”。我們也可用聲明一個空智能指針auto_ptrptest();

當我們對智能指針進行賦值時,如ptest2 = ptest,ptest2會接管ptest原來的內存管理權,ptest會變爲空指針,如果ptest2原來不爲空,則它會釋放原來的資源,基於這個原因,應該避免把auto_ptr放到容器中,因爲算法對容器操作時,很難避免STL內部對容器實現了賦值傳遞操作,這樣會使容器中很多元素被置爲NULL。

判斷一個智能指針是否爲空不能使用if(ptest == NULL),應該使用if(ptest.get() == NULL),如下代碼
C++ 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,這個函數只是把智能指針賦值爲空,但是它原來指向的內存並沒有被釋放,相當於它只是釋放了對資源的所有權。**
	
	那麼當我們想要在中途釋放資源,而不是等到智能指針被析構時才釋放,我們可以使用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 應該具有的功能

	```C++
			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用於交換兩個智能指針的值。

shared_ptr

從名字share就可以看出了資源可以被多個指針共享,它使用計數機制來表明資源被幾個指針共享。可以通過成員函數use_count()來查看資源的所有者個數。除了可以通過new來構造,還可以通過傳入auto_ptr, unique_ptr,weak_ptr來構造。當我們調用release()時,當前指針會釋放資源所有權,計數減一。當計數等於0時,資源會被釋放。

實現:
構造函數中計數初始化爲1;
拷貝構造函數中計數值加1;
賦值運算符中,左邊的對象引用計數減一,右邊的對象引用計數加一;
析構函數中引用計數減一;
在賦值運算符和析構函數中,如果減一後爲0,則調用delete釋放對象。

	```C++
			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

	```C++
			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 pb_; 改爲weak_ptr 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 p = pa->pb_.lock(); p->print();

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章