1. 程式人生 > >【C/C++】c++弱引用 強引用

【C/C++】c++弱引用 強引用

原來,我認為“為什麼會有引用計數這樣的技術”是為了記憶體自動回收和節省記憶體,但是讀完下面的幾節後,記憶體自動回收是一個原因,但是節省記憶體並不是真正的原因,真正的原因是有些物件如果被複制在現實中是不合事實的。


為什麼有引用計數
    C++中存在兩種語義:值語義(value sematics)和物件語義(object sematic),物件語義也可以叫做引用語義(reference sematics)。
值語義,指的是物件的拷貝與原物件無關,就像拷貝int一樣,C++的常用型別資料等都是值語義。
物件語義,指的是面向物件意義下的物件,是禁止拷貝的。
    在設計一個類的時候該類是否可以被拷貝(即具備拷貝建構函式),取決於拷貝後的語義是否成立,比如一個Thread類,拷貝後系統中並不會啟動另外一個執行緒,所以拷貝是禁止的。同樣類似於Employee僱員類也是。
    這麼設計起碼有兩個好處:
    1. 語義合理,有些物件複製是不符合常理的
    2. 節省記憶體


強引用
當物件被建立時,計數為1;每建立一個變數引用該物件時,該物件的計數就增加1;當上述變數銷燬時,物件的計數減1,當計數為0時,這個物件也就被析構了。
強引用計數在很多種情況下都是可以正常工作的,但是也有不湊效的時候,當出現迴圈引用時,就會出現嚴重的問題,以至於出現記憶體洩露,如下程式碼:
[cpp] view plaincopyprint?#include <string>   
#include <iostream>   
#include <boost/shared_ptr.hpp>   
#include <boost/weak_ptr.hpp>   
  
class parent;  
class children;  
  
typedef boost::shared_ptr<parent> parent_ptr;  
typedef boost::shared_ptr<children> children_ptr;  
  
class parent  
{  
public:  
    ~parent() { std::cout <<"destroying parent\n"; }  
  
public:  
    children_ptr children;  
};  
  
class children  
{  
public:  
    ~children() { std::cout <<"destroying children\n"; }  
  
public:  
    parent_ptr parent;  
};  
  
void test()  
{  
    parent_ptr father(new parent());  
    children_ptr son(new children);  
  
    father->children = son;  
    son->parent = father;  
}  
  
void main()  
{  
    std::cout<<"begin test...\n";  
    test();  
    std::cout<<"end test.\n";  

#include <string>
#include <iostream>
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>

class parent;
class children;

typedef boost::shared_ptr<parent> parent_ptr;
typedef boost::shared_ptr<children> children_ptr;

class parent
{
public:
    ~parent() { std::cout <<"destroying parent\n"; }

public:
    children_ptr children;
};

class children
{
public:
    ~children() { std::cout <<"destroying children\n"; }

public:
    parent_ptr parent;
};

void test()
{
    parent_ptr father(new parent());
    children_ptr son(new children);

    father->children = son;
    son->parent = father;
}

void main()
{
    std::cout<<"begin test...\n";
    test();
    std::cout<<"end test.\n";
}
執行該程式可以看到,即使退出了test函式後,由於parent和children物件互相引用,它們的引用計數都是1,不能自動釋放,並且此時這兩個物件再無法訪問到。這就引起了c++中那臭名昭著的記憶體洩漏。
一般來講,解除這種迴圈引用有下面有三種可行的方法:
1. 當只剩下最後一個引用的時候需要手動打破迴圈引用釋放物件。
2. 當parent的生存期超過children的生存期的時候,children改為使用一個普通指標指向parent。
3. 使用弱引用的智慧指標打破這種迴圈引用。
雖然這三種方法都可行,但方法1和方法2都需要程式設計師手動控制,麻煩且容易出錯。下面就介紹弱引用

弱引用
boost::weak_ptr<T>是boost提供的一個弱引用的智慧指標,它的宣告可以簡化如下:

[cpp] view plaincopyprint?namespace boost {  
  
    template<typename T> class weak_ptr {  
    public:  
        template <typename Y>  
        weak_ptr(const shared_ptr<Y>& r);  
  
        weak_ptr(const weak_ptr& r);  
  
        ~weak_ptr();  
  
        T* get() const;   
        bool expired() const;   
        shared_ptr<T> lock() const;  
    };   

namespace boost {

    template<typename T> class weak_ptr {
    public:
        template <typename Y>
        weak_ptr(const shared_ptr<Y>& r);

        weak_ptr(const weak_ptr& r);

        ~weak_ptr();

        T* get() const; 
        bool expired() const; 
        shared_ptr<T> lock() const;
    }; 
}
可以看到,boost::weak_ptr必須從一個boost::share_ptr或另一個boost::weak_ptr轉換而來,這也說明,進行該物件的記憶體管理的是那個強引用的boost::share_ptr。boost::weak_ptr只是提供了對管理物件的一個訪問手段。boost::weak_ptr除了對所管理物件的基本訪問功能(通過get()函式)外,還有兩個常用的功能函式:expired()用於檢測所管理的物件是否已經釋放;lock()用於獲取所管理的物件的強引用指標。
由於弱引用不更改引用計數,類似普通指標,只要把迴圈引用的一方使用弱引用,即可解除迴圈引用。對於上面的那個例子來說,只要把children的定義改為如下方式,即可解除迴圈引用:

[cpp] view plaincopyprint?class children  
{  
public:  
    ~children() { std::cout <<"destroying children\n"; }  
  
public:  
    boost::weak_ptr<parent> parent;  
}; 

class children
{
public:
    ~children() { std::cout <<"destroying children\n"; }

public:
    boost::weak_ptr<parent> parent;
};最後值得一提的是,雖然通過弱引用指標可以有效的解除迴圈引用,但這種方式必須在程式設計師能預見會出現迴圈引用的情況下才能使用,也可以是說這個僅僅是一種編譯期的解決方案,如果程式在執行過程中出現了迴圈引用,還是會造成記憶體洩漏的。因此,不要認為只要使用了智慧指標便能杜絕記憶體洩漏。畢竟,對於C++來說,由於沒有垃圾回收機制,記憶體洩漏對每一個程式設計師來說都是一個非常頭痛的問題。