C++ - 強引用和弱引用


原來,我認為“為什么會有引用計數這樣的技術”是為了內存自動回收和節省內存,但是讀完下面的幾節后,內存自動回收是一個原因,但是節省內存並不是真正的原因,真正的原因是有些對象如果被復制在現實中是不合事實的。

 

為什么有引用計數

    C++中存在兩種語義:值語義(value sematics)和對象語義(object sematic),對象語義也可以叫做引用語義(reference sematics)。
值語義,指的是對象的拷貝與原對象無關,就像拷貝int一樣,C++的常用類型數據等都是值語義。
對象語義,指的是面向對象意義下的對象,是禁止拷貝的。

    在設計一個類的時候該類是否可以被拷貝(即具備拷貝構造函數),取決於拷貝后的語義是否成立,比如一個Thread類,拷貝后系統中並不會啟動另外一個線程,所以拷貝是禁止的。同樣類似於Employee雇員類也是。

    這么設計起碼有兩個好處:

    1. 語義合理,有些對象復制是不符合常理的

    2. 節省內存

 

 

強引用

當對象被創建時,計數為1;每創建一個變量引用該對象時,該對象的計數就增加1;當上述變量銷毀時,對象的計數減1,當計數為0時,這個對象也就被析構了。
強引用計數在很多種情況下都是可以正常工作的,但是也有不湊效的時候,當出現循環引用時,就會出現嚴重的問題,以至於出現內存泄露,如下代碼:
[cpp]  view plain copy
  1. #include   
  2. #include   
  3. #include   
  4. #include   
  5.   
  6. class parent;  
  7. class children;  
  8.   
  9. typedef boost::shared_ptr parent_ptr;  
  10. typedef boost::shared_ptr children_ptr;  
  11.   
  12. class parent  
  13. {  
  14. public:  
  15.     ~parent() { std::cout <<"destroying parent\n"; }  
  16.   
  17. public:  
  18.     children_ptr children;  
  19. };  
  20.   
  21. class children  
  22. {  
  23. public:  
  24.     ~children() { std::cout <<"destroying children\n"; }  
  25.   
  26. public:  
  27.     parent_ptr parent;  
  28. };  
  29.   
  30. void test()  
  31. {  
  32.     parent_ptr father(new parent());  
  33.     children_ptr son(new children);  
  34.   
  35.     father->children = son;  
  36.     son->parent = father;  
  37. }  
  38.   
  39. void main()  
  40. {  
  41.     std::cout<<"begin test...\n";  
  42.     test();  
  43.     std::cout<<"end test.\n";  
  44. }  

運行該程序可以看到,即使退出了test函數后,由於parent和children對象互相引用,它們的引用計數都是1,不能自動釋放,並且此時這兩個對象再無法訪問到。這就引起了c++中那臭名昭著的內存泄漏。
一般來講,解除這種循環引用有下面有三種可行的方法:
1. 當只剩下最后一個引用的時候需要手動打破循環引用釋放對象。
2. 當parent的生存期超過children的生存期的時候,children改為使用一個普通指針指向parent。
3. 使用弱引用的智能指針打破這種循環引用。
雖然這三種方法都可行,但方法1和方法2都需要程序員手動控制,麻煩且容易出錯。下面就介紹弱引用
 
在多線程程序中,一個對象如果被多個線程訪問,一般使用shared_ptr,通過引用計數來保證對象不被錯誤的釋放導致其他線程訪問出現問題。
但這種引用計數解決不了循環引用的問題
 

弱引用

boost::weak_ptr是boost提供的一個弱引用的智能指針,它的聲明可以簡化如下:
[cpp]  view plain copy
  1. namespace boost {  
  2.   
  3.     template<</span>typename T> class weak_ptr {  
  4.     public:  
  5.         template <</span>typename Y>  
  6.         weak_ptr(const shared_ptr& r);  
  7.   
  8.         weak_ptr(const weak_ptr& r);  
  9.   
  10.         ~weak_ptr();  
  11.   
  12.         T* get() const;   
  13.         bool expired() const;   
  14.         shared_ptr lock() const;  
  15.     };   
  16. }  
定義變量:
shared_ptr<T>  t(new T);
weak_ptr<T> ptr(t); // t為一個T對象 
則當t被銷毀時,ptr 被自動置為無效。使用方法如下:
if ( shard_ptr<T>  safePtr  = ptr.lock() )  safePtr->Fun();
 
可以看到,boost::weak_ptr必須從一個boost::share_ptr或另一個boost::weak_ptr轉換而來,這也說明,進行該對象的內存管理的是那個強引用的boost::share_ptr。boost::weak_ptr只是提供了對管理對象的一個訪問手段。boost::weak_ptr除了對所管理對象的基本訪問功能(通過get()函數)外,還有兩個常用的功能函數:expired()用於檢測所管理的對象是否已經釋放;lock()用於獲取所管理的對象的強引用指針。
由於弱引用不更改引用計數,類似普通指針,只要把循環引用的一方使用弱引用,即可解除循環引用。對於上面的那個例子來說,只要把children的定義改為如下方式,即可解除循環引用:
[cpp]  view plain copy
  1. class children  
  2. {  
  3. public:  
  4.     ~children() { std::cout <<"destroying children\n"; }  
  5.   
  6. public:  
  7.     boost::weak_ptr parent;  
  8. };  
最后值得一提的是,雖然通過弱引用指針可以有效的解除循環引用,但這種方式必須在程序員能預見會出現循環引用的情況下才能使用,也可以是說 弱引用僅僅是一種編譯期的解決方案,如果程序在運行過程中出現了循環引用,還是會造成內存泄漏的。因此,不要認為只要使用了智能指針便能杜絕內存泄漏。畢竟,對於C++來說,由於沒有垃圾回收機制,內存泄漏對每一個程序員來說都是一個非常頭痛的問題。
 
弱引用:它僅僅是對象 存在時候的引用,當對象不存在時弱引用能夠檢測到,從而避免非法訪問,弱引用也不會修改對象的引用計數。這意味這弱引用它並不對對象的內存進行管理,在功能上類似於普通指針,然而一個比較大的區別是,弱引用能檢測到所管理的對象是否已經被釋放,從而避免訪問非法內存。

 


免責聲明!

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



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