日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > c/c++ >内容正文

c/c++

窥见C++11智能指针

發布時間:2024/2/28 c/c++ 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 窥见C++11智能指针 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.




導語: C++指針的內存管理相信是大部分C++入門程序員的夢魘,受到Boost的啟發,C++11標準推出了智能指針,讓我們從指針的內存管理中釋放出來,幾乎消滅所有new和delete。既然智能指針如此強大,今天我們來一窺智能指針的原理以及在多線程操作中需要注意的細節。


智能指針的由來

在遠古時代,C++使用了指針這把雙刃劍,既可以讓程序員精確地控制堆上每一塊內存,也讓程序更容易發生crash,大大增加了使用指針的技術門檻。因此,從C++98開始便推出了auto_ptr,對裸指針進行封裝,讓程序員無需手動釋放指針指向的內存區域,在auto_ptr生命周期結束時自動釋放,然而,由于auto_ptr在轉移指針所有權后會產生野指針,導致程序運行時crash,如下面示例代碼所示:

auto_ptr<int> p1(new int(10)); auto_ptr<int> p2 = p1; //轉移控制權 *p1 += 10; //crash,p1為空指針,可以用p1->get判空做保護


因此在C++11又推出了unique_ptr、shared_ptr、weak_ptr三種智能指針,慢慢取代auto_ptr。


unique_ptr的使用

unique_ptr是auto_ptr的繼承者,對于同一塊內存只能有一個持有者,而unique_ptr和auto_ptr唯一區別就是unique_ptr不允許賦值操作,也就是不能放在等號的右邊(函數的參數和返回值例外),這一定程度避免了一些誤操作導致指針所有權轉移,然而,unique_str依然有提供所有權轉移的方法move,調用move后,原unique_ptr就會失效,再用其訪問裸指針也會發生和auto_ptr相似的crash,如下面示例代碼,所以,即使使用了unique_ptr,也要慎重使用move方法,防止指針所有權被轉移。

unique_ptr<int> up(new int(5)); //auto up2 = up; // 編譯錯誤 auto up2 = move(up); cout << *up << endl; //crash,up已經失效,無法訪問其裸指針


除了上述用法,unique_ptr還支持創建動態數組。在C++中,創建數組有很多方法,如下所示:

// 靜態數組,在編譯時決定了數組大小 int arr[10]; // 通過指針創建在堆上的數組,可在運行時動態指定數組大小,但需要手動釋放內存 int *arr = new int[10]; // 通過std::vector容器創建動態數組,無需手動釋放數組內存 vector<int> arr(10); // 通過unique_ptr創建動態數組,也無需手動釋放數組內存,比vector更輕量化 unique_ptr<int[]> arr(new int[10]);


這里需要注意的是,不管vector還是unique_ptr,雖然可以幫我們自動釋放數組內存,但如果數組的元素是復雜數據類型時,我們還需要在其析構函數中正確釋放內存。


真正的智能指針:shared_ptr

auto_ptr和unique_ptr都有或多或少的缺陷,因此C++11還推出了shared_ptr,這也是目前工程內使用最多最廣泛的智能指針,他使用引用計數(感覺有參考Objective-C的嫌疑),實現對同一塊內存可以有多個引用,在最后一個引用被釋放時,指向的內存才釋放,這也是和unique_ptr最大的區別。


另外,使用shared_ptr過程中有幾點需要注意:

  • 構造shared_ptr的方法,如下示例代碼所示,我們盡量使用shared_ptr構造函數或者make_shared的方式創建shared_ptr,禁止使用裸指針賦值的方式,這樣會shared_ptr難于管理指針的生命周期。
// 使用裸指針賦值構造,不推薦,裸指針被釋放后,shared_ptr就野了,不能完全控制裸指針的生命周期,失去了智能指針價值 int *p = new int(10); shared_ptr<int>sp = p; delete p; // sp將成為野指針,使用sp將crash // 將裸指針作為匿名指針傳入構造函數,一般做法,讓shared_ptr接管裸指針的生命周期,更安全 shared_ptr<int>sp1(new int(10)); // 使用make_shared,推薦做法,更符合工廠模式,可以連代碼中的所有new,更高效;方法的參數是用來初始化模板類 shared_ptr<int>sp2 = make_shared<int>(10);


  • 禁止使用指向shared_ptr的裸指針,也就是智能指針的指針,這聽起來就很奇怪,但開發中我們還需要注意,使用shared_ptr的指針指向一個shared_ptr時,引用計數并不會加一,操作shared_ptr的指針很容易就發生野指針異常。
shared_ptr<int>sp = make_shared<int>(10); cout << sp.use_count() << endl; //輸出1 shared_ptr<int> *sp1 = &sp; cout << (*sp1).use_count() << endl; //輸出依然是1 (*sp1).reset(); //sp成為野指針 cout << *sp << endl; //crash


  • 使用shared_ptr創建動態數組,在介紹unique_ptr時我們就講過創建動態數組,而shared_ptr同樣可以做到,不過稍微復雜一點,如下代碼所示,除了要顯示指定析構方法外(因為默認是T的析構函數,不是T[]),另外對外的數據類型依然是shared_ptr<T>,非常有迷惑性,看不出來是數組,最后不能直接使用下標讀寫數組,要先get()獲取裸指針才可以使用下標。所以,不推薦使用shared_ptr來創建動態數組,盡量使用unique_ptr,這可是unique_ptr為數不多的優勢了。
template &lt;typename T&gt; shared_ptr&lt;T&gt; make_shared_array(size_t size) { return shared_ptr&lt;T&gt;(new T[size], default_delete&lt;T[]&gt;()); } shared_ptr&lt;int&gt;sp = make_shared_array(10); //看上去是shared&lt;int&gt;類型,實際上是數組 sp.get()[0] = 100; //不能直接使用下標讀寫數組元素,需要通過get()方法獲取裸指針后再操作


  • 用shared_ptr實現多態,在我們使用裸指針時,實現多態就免不了定義虛函數,那么用shared_ptr時也不例外,不過有一處是可以省下的,就是析構函數我們不需要定義為虛函數了,如下面代碼所示:
class A { public: ~A() { cout &lt;&lt; "dealloc A" &lt;&lt; endl; } }; class B : public A { public: ~B() { cout &lt;&lt; "dealloc B" &lt;&lt; endl; } }; int main(int argc, const char * argv[]) { A *a = new B(); delete a; //只打印dealloc A shared_ptr&lt;A&gt;spa = make_shared&lt;B&gt;(); //析構spa是會先打印dealloc B,再打印dealloc A return 0; }


  • 循環引用,筆者最先接觸引用計數的語言就是Objective-C,而OC中最常出現的內存問題就是循環引用,如下面代碼所示,A中引用B,B中引用A,spa和spb的強引用計數永遠大于等于1,所以直到程序退出前都不會被退出,這種情況有時候在正常的業務邏輯中是不可避免的,而解決循環引用的方法最有效就是改用weak_ptr,具體可見下一章。
class A { public: shared_ptr&lt;B&gt; b; }; class B { public: shared_ptr&lt;A&gt; a; }; int main(int argc, const char * argv[]) { shared_ptr&lt;A&gt; spa = make_shared&lt;A&gt;(); shared_ptr&lt;B&gt; spb = make_shared&lt;B&gt;(); spa-&gt;b = spb; spb-&gt;a = spa; return 0; } //main函數退出后,spa和spb強引用計數依然為1,無法釋放


剛柔并濟:weak_ptr

正如上一章提到,使用shared_ptr過程中有可能會出現循環引用,關鍵原因是使用shared_ptr引用一個指針時會導致強引用計數+1,從此該指針的生命周期就會取決于該shared_ptr的生命周期,然而,有些情況我們一個類A里面只是想引用一下另外一個類B的對象,類B對象的創建不在類A,因此類A也無需管理類B對象的釋放,這個時候weak_ptr就應運而生了,使用shared_ptr賦值給一個weak_ptr不會增加強引用計數(strong_count),取而代之的是增加一個弱引用計數(weak_count),而弱引用計數不會影響到指針的生命周期,這就解開了循環引用,上一章最后的代碼使用weak_ptr可改造為如下代碼。

class A { public: shared_ptr&lt;B&gt; b; }; class B { public: weak_ptr&lt;A&gt; a; }; int main(int argc, const char * argv[]) { shared_ptr&lt;A&gt; spa = make_shared&lt;A&gt;(); shared_ptr&lt;B&gt; spb = make_shared&lt;B&gt;(); spa-&gt;b = spb; //spb強引用計數為2,弱引用計數為1 spb-&gt;a = spa; //spa強引用計數為1,弱引用計數為2 return 0; } //main函數退出后,spa先釋放,spb再釋放,循環解開了


使用weak_ptr也有需要注意的點,因為既然weak_ptr不負責裸指針的生命周期,那么weak_ptr也無法直接操作裸指針,我們需要先轉化為shared_ptr,這就和OC的Strong-Weak Dance有點像了,具體操作如下:

shared_ptr&lt;int&gt; spa = make_shared&lt;int&gt;(10); weak_ptr&lt;int&gt; spb = spa; //weak_ptr無法直接使用裸指針創建 if (!spb.expired()) { //weak_ptr最好判斷是否過期,使用expired或use_count方法,前者更快 *spb.lock() += 10; //調用weak_ptr轉化為shared_ptr后再操作裸指針 } cout &lt;&lt; *spa &lt;&lt; endl; //20


智能指針原理

看到這里,智能指針的用法基本介紹完了,后面筆者來粗淺地分析一下為什么智能指針可以有效幫我們管理裸指針的生命周期。


使用棧對象管理堆對象

在C++中,內存會分為三部分,堆、棧和靜態存儲區,靜態存儲區會存放全局變量和靜態變量,在程序加載時就初始化,而堆是由程序員自行分配,自行釋放的,例如我們使用裸指針分配的內存;而最后棧是系統幫我們分配的,所以也會幫我們自動回收。因此,智能指針就是利用這一性質,通過一個棧上的對象(shared_ptr或unique_ptr)來管理一個堆上的對象(裸指針),在shared_ptr或unique_ptr的析構函數中判斷當前裸指針的引用計數情況來決定是否釋放裸指針。


shared_ptr引用計數的原理一開始筆者以為引用計數是放在shared_ptr這個模板類中,但是細想了一下,如果這樣將shared_ptr賦值給另一個shared_ptr時,是怎么做到兩個shared_ptr的引用計數同時加1呢,讓等號兩邊的shared_ptr中的引用計數同時加1?不對,如果還有第二個shared_ptr再賦值給第三個shared_ptr那怎么辦呢?或許通過下面的類圖便清楚個中奧秘。

[ boost中shared_ptr與weak_ptr類圖 ]


我們重點關注shared_ptr<T>的類圖,它就是我們可以直接操作的類,這里面包含裸指針T*,還有一個shared_count的對象,而shared_count對象還不是最終的引用計數,它只是包含了一個指向sp_counted_base的指針,這應該就是真正存放引用計數的地方,包括強應用計數和弱引用計數,而且shared_count中包含的是sp_counted_base的指針,不是對象,這也就意味著假如shared_ptr<T> a = b,那么a和b底層pi_指針指向的是同一個sp_counted_base對象,這就很容易做到多個shared_ptr的引用計數永遠保持一致了。


多線程安全本章所說的線程安全有兩種情況:

多個線程操作多個不同的shared_ptr對象

C++11中聲明了shared_ptr的計數操作具有原子性,不管是賦值導致計數增加還是釋放導致計數減少,都是原子性的,這個可以參考sp_counted_base的源碼,因此,基于這個特性,假如有多個shared_ptr共同管理一個裸指針,那么多個線程分別通過不同的shared_ptr進行操作是線程安全的。


多個線程操作同一個shared_ptr對象

同樣的道理,既然C++11只負責sp_counted_base的原子性,那么shared_ptr本身就沒有保證線程安全了,加入兩個線程同時訪問同一個shared_ptr對象,一個進行釋放(reset),另一個讀取裸指針的值,那么最后的結果就不確定了,很有可能發生野指針訪問crash。


總結

以上是生活随笔為你收集整理的窥见C++11智能指针的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。