C++ 中shared_ptr循環引用計數問題


轉自 https://blog.csdn.net/daniel_ustc/article/details/23096229

c++智能指針介紹

由於 C++ 語言沒有自動內存回收機制,程序員每次 new 出來的內存都要手動 delete,比如流程太復雜,最終導致沒有 delete,異常導致程序過早退出,沒有執行 delete 的情況並不罕見,並造成內存泄露。如此c++引入智能指針 ,智能指針即是C++ RAII的一種應用,可用於動態資源管理,資源即對象的管理策略。 智能指針在 <memory> 標頭文件的 std 命名空間中定義。 它們對 RAII獲取資源即初始化編程慣用法至關重要。RAII 的主要原則是為所有堆分配資源提供所有權,例如動態分配內存或系統對象句柄、析構函數包含要刪除或釋放資源的代碼的堆棧分配對象,以及任何相關清理代碼。

c++智能指針類別

c++ 智能指針主要包括:unique_ptr,shared_ptr, weak_ptr, 這三種,其中auto_ptr 已被遺棄。

unique_ptr
只允許基礎指針的一個所有者。 可以移到新所有者(具有移動語義),但不會復制或共享(即我們無法得到指向同一個對象的兩個unique_ptr)。 替換已棄用的 auto_ptr。 相較於 boost::scoped_ptr。 unique_ptr 小巧高效;大小等同於一個指針,支持 rvalue 引用,從而可實現快速插入和對 STL 集合的檢索。 頭文件:<memory>。

使用unique_ptr,可以實現以下功能:

1、為動態申請的內存提供異常安全。
2、將動態申請內存的所有權傳遞給某個函數。
3、從某個函數返回動態申請內存的所有權。

4、在容器中保存指針。
5、所有auto_ptr應該具有的(但無法在C++ 03中實現的)功能。

如下代碼所示:


    
    
    
            
  1. class A;
  2. // 如果程序執行過程中拋出了異常,unique_ptr就會釋放它所指向的對象
  3. // 傳統的new 則不行
  4. unique_ptr<A> fun1()
  5. {
  6. unique_ptr p(new A);
  7. //do something
  8. return p;
  9. }
  10. void fun2()
  11. { // unique_ptr具有移動語義
  12. unique_ptr<A> p = f(); // 使用移動構造函數
  13. // do something
  14. } // 在函數退出的時候,p以及它所指向的對象都被刪除釋放
 shared_ptr
采用引用計數的智能指針。 shared_ptr基於“引用計數”模型實現,多個shared_ptr可指向同一個動態對象,並維護了一個共享的引用計數器,記錄了引用同一對象的shared_ptr實例的數量。當最后一個指向動態對象的shared_ptr銷毀時,會自動銷毀其所指對象(通過delete操作符)。shared_ptr的默認能力是管理動態內存,但支持自定義的Deleter以實現個性化的資源釋放動作。頭文件:<memory>。

基本操作:shared_ptr的創建、拷貝、綁定對象的變更(reset)、shared_ptr的銷毀(手動賦值為nullptr或離開作用域)、指定deleter等操作。

 shared_ptr的創建,有兩種方式,一,使用函數make_shared(會根據傳遞的參數調用動態對象的構造函數);二,使用構造函數(可從原生指針、unique_ptr、另一個shared_ptr創建)


    
    
    
            
  1. shared_ptr< int> p1 = make_shared< int>( 1); // 通過make_shared函數
  2. shared_ptr< int> p2( new int( 2)); // 通過原生指針構造
此外智能指針若為“空“,即不指向任何對象,則為false,否則為true,可作為條件判斷。可以通過兩種方式指定deleter,一是構造shared_ptr時,二是使用reset方法時。可以重載的operator->, operator *,以及其他輔助操作如unique()、use_count(), get()等成員方法。

 weak_ptr
結合 shared_ptr 使用的特例智能指針。 weak_ptr 提供對一個或多個 shared_ptr 實例所屬對象的訪問,但是,不參與引用計數。 如果您想要觀察對象但不需要其保持活動狀態,請使用該實例。 在某些情況下需要斷開 shared_ptr 實例間的循環引用。 頭文件:<memory>。

weak_ptr的用法如下:

weak_ptr用於配合shared_ptr使用,並不影響動態對象的生命周期,即其存在與否並不影響對象的引用計數器。weak_ptr並沒有重載operator->和operator *操作符,因此不可直接通過weak_ptr使用對象。提供了expired()與lock()成員函數,前者用於判斷weak_ptr指向的對象是否已被銷毀,后者返回其所指對象的shared_ptr智能指針(對象銷毀時返回”空“shared_ptr)。循環引用的場景:如二叉樹中父節點與子節點的循環引用,容器與元素之間的循環引用等。

智能指針的循環引用

循環引用問題可以參考這個鏈接上的問題理解,“循環引用”簡單來說就是:兩個對象互相使用一個shared_ptr成員變量指向對方的會造成循環引用。導致引用計數失效。下面給段代碼來說明循環引用:


    
    
    
            
  1. #include <iostream>
  2. #include <memory>
  3. using namespace std;
  4. class B;
  5. class A
  6. {
  7. public: // 為了省去一些步驟這里 數據成員也聲明為public
  8. //weak_ptr<B> pb;
  9. shared_ptr<B> pb;
  10. void doSomthing()
  11. {
  12. // if(pb.lock())
  13. // {
  14. //
  15. // }
  16. }
  17. ~A()
  18. {
  19. cout << "kill A\n";
  20. }
  21. };
  22. class B
  23. {
  24. public:
  25. //weak_ptr<A> pa;
  26. shared_ptr<A> pa;
  27. ~B()
  28. {
  29. cout << "kill B\n";
  30. }
  31. };
  32. int main(int argc, char** argv)
  33. {
  34. shared_ptr<A> sa( new A());
  35. shared_ptr<B> sb( new B());
  36. if(sa && sb)
  37. {
  38. sa->pb=sb;
  39. sb->pa=sa;
  40. }
  41. cout<< "sa use count:"<<sa.use_count()<< endl;
  42. return 0;
  43. }
上面的代碼運行結果為:sa use count:2, 注意此時sa,sb都沒有釋放,產生了內存泄露問題!!!

即A內部有指向B,B內部有指向A,這樣對於A,B必定是在A析構后B才析構,對於B,A必定是在B析構后才析構A,這就是循環引用問題,違反常規,導致內存泄露。

一般來講,解除這種循環引用有下面有三種可行的方法(參考):
1. 當只剩下最后一個引用的時候需要手動打破循環引用釋放對象。
2. 當A的生存期超過B的生存期的時候,B改為使用一個普通指針指向A。
3. 使用弱引用的智能指針打破這種循環引用。
雖然這三種方法都可行,但方法1和方法2都需要程序員手動控制,麻煩且容易出錯。我們一般使用第三種方法:弱引用的智能指針weak_ptr。

強引用和弱引用
一個強引用當被引用的對象活着的話,這個引用也存在(就是說,當至少有一個強引用,那么這個對象就不能被釋放)。share_ptr就是強引用。相對而言,弱引用當引用的對象活着的時候不一定存在。僅僅是當它存在的時候的一個引用。弱引用並不修改該對象的引用計數,這意味這弱引用它並不對對象的內存進行管理,在功能上類似於普通指針,然而一個比較大的區別是,弱引用能檢測到所管理的對象是否已經被釋放,從而避免訪問非法內存。

使用weak_ptr來打破循環引用

代碼如下:


    
    
    
            
  1. #include <iostream>
  2. #include <memory>
  3. using namespace std;
  4. class B;
  5. class A
  6. {
  7. public: // 為了省去一些步驟這里 數據成員也聲明為public
  8. weak_ptr<B> pb;
  9. //shared_ptr<B> pb;
  10. void doSomthing()
  11. {
  12. shared_ptr<B> pp = pb.lock();
  13. if(pp) //通過lock()方法來判斷它所管理的資源是否被釋放
  14. {
  15. cout<< "sb use count:"<<pp.use_count()<< endl;
  16. }
  17. }
  18. ~A()
  19. {
  20. cout << "kill A\n";
  21. }
  22. };
  23. class B
  24. {
  25. public:
  26. //weak_ptr<A> pa;
  27. shared_ptr<A> pa;
  28. ~B()
  29. {
  30. cout << "kill B\n";
  31. }
  32. };
  33. int main(int argc, char** argv)
  34. {
  35. shared_ptr<A> sa( new A());
  36. shared_ptr<B> sb( new B());
  37. if(sa && sb)
  38. {
  39. sa->pb=sb;
  40. sb->pa=sa;
  41. }
  42. sa->doSomthing();
  43. cout<< "sb use count:"<<sb.use_count()<< endl;
  44. return 0;
  45. }

需要知道的

weak_ptr除了對所管理對象的基本訪問功能(通過get()函數)外,還有兩個常用的功能函數:expired()用於檢測所管理的對象是否已經釋放;lock()用於獲取所管理的對象的強引用指針。不能直接通過weak_ptr來訪問資源。那么如何通過weak_ptr來間接訪問資源呢?答案是:在需要訪問資源的時候weak_ptr為你生成一個shared_ptr,shared_ptr能夠保證在shared_ptr沒有被釋放之前,其所管理的資源是不會被釋放的。創建shared_ptr的方法就是lock()方法。

參考

http://msdn.microsoft.com/zh-cn/library/hh279674.aspx

http://www.dewen.org/q/8560/%E5%85%B3%E4%BA%8E%E9%81%BF%E5%85%8D%E5%BE%AA%E7%8E%AF%E5%BC%95%E7%94%A8


免責聲明!

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



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