從零開始學C++之boost庫(一):詳解 boost 庫智能指針(scoped_ptr 、shared_ptr 、weak_ptr 源碼分析)

一、boost 智能指針


智能指針是利用RAII(Resource Acquisition Is Initialization:資源獲取即初始化)來管理資源。關於RAII的討論可以參考前面的


。在使用boost庫之前應該先下載後放在某個路徑,並在VS 包含目錄中添加。下面是boost 庫裏面的智能指針:




(一)、scoped_ptr<T>

先來看例程:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <boost/scoped_ptr.hpp>
#include <iostream>
using namespace std;

class X
{
public:
    X()
    {
        cout << "X ..." << endl;
    }
    ~X()
    {
        cout << "~X ..." << endl;
    }
};

int main(void)
{
    cout << "Entering main ..." << endl;
    {
        boost::scoped_ptr<X> pp(new X);

        //boost::scoped_ptr<X> p2(pp); //Error:所有權不能轉移
    }
    cout << "Exiting main ..." << endl;

    return 0;
}



來稍微看一下scoped_ptr 的簡單定義:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
namespace boost
{

    template<typename T> class scoped_ptr : noncopyable
    {
    private:

        T *px;

        scoped_ptr(scoped_ptr const &);
        scoped_ptr &operator=(scoped_ptr const &);

        typedef scoped_ptr<T> this_type;

        void operator==( scoped_ptr const & ) const;
        void operator!=( scoped_ptr const & ) const;
    public:
        explicit scoped_ptr(T *p = 0);
        ~scoped_ptr();

        explicit scoped_ptr( std::auto_ptr<T> p ): px( p.release() );
        void reset(T *p = 0);

        T &operator*() const;
        T *operator->() const;
        T *get() const;

        void swap(scoped_ptr &b);
    };

    template<typename T>
    void swap(scoped_ptr<T> &a, scoped_ptr<T> &b);
}

auto_ptr類似,內部也有一個T* px; 成員 ,智能指針對象pp 生存期到了,調用析構函數,在析構函數內會delete  px; 如下面所說:


 scoped_ptr mimics a built-in pointer except that it guarantees deletion of the object pointed to, either on destruction of the scoped_ptr or via an


 explicit reset(). scoped_ptr is a simple solution for simple needs; use shared_ptr or std::auto_ptr if your needs are more complex.


從上面的話可以得知當調用reset() 函數時也能夠釋放堆對象,如何實現的呢?

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
void reset(T *p = 0)  // never throws
{
    BOOST_ASSERT( p == 0 || p != px ); // catch self-reset errors
    this_type(p).swap(*this);
}
void swap(scoped_ptr &b)  // never throws
{
    T *tmp = b.px;
    b.px = px;
    px = tmp;
}

typedef scoped_ptr<T> this_type;  當調用pp.reset(),reset 函數構造一個臨時對象,它的成員px=0, 在swap 函數中調換 pp.px  與 


(this_type)(p).px, 即現在pp.px = 0; //解綁  


臨時對象接管了裸指針(即所有權可以交換),reset 函數返回,棧上的臨時對象析構,調用析構函數,進而delete px;


另外拷貝構造函數和operator= 都聲明爲私有,故所有權不能轉移,且因爲容器的push_back 函數需要調用拷貝構造函數,故也不能


將scoped_ptr 放進vector,這點與auto_ptr 相同(不能共享所有權)。此外,還可以使用 auto_ptr 對象 構造一個scoped_ptr 對象:


scoped_ptr( std::auto_ptr<T> p ): px( p.release() );


由於scoped_ptr是通過delete來刪除所管理對象的,而數組對象必須通過deletep[]來刪除,因此boost::scoped_ptr是不能管理數組對象的,如果


要管理數組對象需要使用boost::scoped_array類。


boost::scoped_ptr和std::auto_ptr的功能和操作都非常類似,如何在他們之間選取取決於是否需要轉移所管理的對象的所有權(如是否需要作爲


函數的返回值)。如果沒有這個需要的話,大可以使用boost::scoped_ptr,讓編譯器來進行更嚴格的檢查,來發現一些不正確的賦值操作。


(二)、shared_ptr<T>


An enhanced relative of scoped_ptr with reference counted copy semantics. The object pointed to is deleted when the last 

shared_ptr pointing to it  is destroyed or reset.


先來看例程:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include <boost/shared_ptr.hpp>
#include <iostream>
using namespace std;

class X
{
public:
    X()
    {
        cout << "X ..." << endl;
    }
    ~X()
    {
        cout << "~X ..." << endl;
    }
};

int main(void)
{
    cout << "Entering main ..." << endl;
    boost::shared_ptr<X> p1(new X);
    cout << p1.use_count() << endl;
    boost::shared_ptr<X> p2 = p1;
    //boost::shared_ptr<X> p3;
    //p3 = p1;

    cout << p2.use_count() << endl;
    p1.reset();
    cout << p2.use_count() << endl;
    p2.reset();
    cout << "Exiting main ..." << endl;
    return 0;
}

圖示上述程序的過程也就是:


再深入一點,看源碼,shared_ptr 的實現 比 scoped_ptr 要複雜許多,涉及到多個類,下面就不貼完整源碼,看下面的類圖:


執行 boost::shared_ptr<X> p1(new X); 這一行之後:

而執行 p1.use_count(); 先是 pn.use_count();  接着 pi_ != 0? pi_->use_count(): 0;  return use_count_; 即返回1.


接着執行  boost::shared_ptr<X> p2 = p1; 


本想跟蹤shared_ptr 的拷貝構造函數,在當行設置斷點後F11直接跳過了,說明是shared_ptr類沒有實現拷貝構造函數,使用的是編譯器默認的拷


貝構造函數,那如何跟蹤呢?如果你的C++基礎比較好,可以想到拷貝構造函數跟構造函數一樣,如果有對象成員是需要先構造對象成員的(這一點


也可以從調用堆棧上看出),故可以在shared_count 類的拷貝構造函數設置斷點,然後就可以跟蹤進去,如下的代碼:

 C++ Code 
1
2
3
4
5
6
7
8
// shared_count
shared_count(shared_count const &r): pi_(r.pi_)  // nothrow
{
    if( pi_ != 0 ) pi_->add_ref_copy();
}
// sp_counted_base
void add_ref_copy()
{
    BOOST_INTERLOCKED_INCREMENT( &use_count_ );
}

故p2.pn.pi_ 也指向 唯一的一個 sp_counted_impl_p 對象,且use_count_ 增1.


再者,shared_ptr 類的默認拷貝構造函數是淺拷貝,故現在p2.px 也指向 X.


由於p2 和 p1 共享一個sp_counted_impl_p 對象,所以此時無論打印p2.use_count(); 還是 p1.use_count(); 都是2。


接着執行p1.reset(); 


 C++ Code 
1
2
3
4
5
6
7
8
9
// shared_ptr
void reset() // never throws in 1.30+
{
    this_type().swap(*this);
}
void swap(shared_ptr<T> &other)  // never throws
{
    std::swap(px, other.px);
    pn.swap(other.pn);
}

this_type() 構造一個臨時對象,px = 0, pn.pi_ =  0; 然後swap交換p1 與 臨時對象的成員,即現在p1.px = 0; p1.pn.p1_ = 0; 如上圖。


reset 函數返回,臨時對象需要析構,但跟蹤時卻發現直接返回了,原因跟上面的一樣,因爲shared_ptr 沒有實現析構函數,調用的是默認的析構函


數,與上面拷貝函數同樣的道理,可以在shared_count 類析構函數設置斷點,因爲pn 是對象成員,故析構函數也會被調用。如下代碼:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
//shared_count
~shared_count() // nothrow
{
    if( pi_ != 0 ) pi_->release();
}

// sp_counted_base
void release() // nothrow
{
    if( BOOST_INTERLOCKED_DECREMENT( &use_count_ ) == 0 )
    {
        dispose();
        weak_release();
    }
}

現在use_count_ 減爲1,但還不爲0,故 dispose(); 和 weak_release(); 兩個函數沒有被調用。當然此時打印 p2.use_count() 就爲1 了。


最後 p2.reset(); 跟p1.reset(); 同樣的流程,只不過現在執行到release 時,use_count_ 減1 爲0;需要繼續執行dispose(); 和 


weak_release();  如下代碼:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

//sp_counted_impl_p
virtual void dispose() // nothrow
{
    boost::checked_delete( px_ );
}
//sp_counted_base
void weak_release() // nothrow
{
    if( BOOST_INTERLOCKED_DECREMENT( &weak_count_ ) == 0 )
    {
        destroy();
    }
}

virtual void destroy() // nothrow
{
    delete this;
}

在check_delete 中會 delete px_; 也就是析構 X。接着因爲weak_count_ 減1 爲0, 故執行destroy();  函數裏面delete this; 即析構自身


(sp_counted_impl_p 對象是在堆上分配的)。


說到這裏,我們也可以明白,即使最後沒有調用p2.reset(); 當p2 棧上對象生存期到, 需要調用shared_ptr 類析構函數,進而調用shared_count 類析


構函數,所以執行的結果也是跟reset() 一樣的,只不過少了臨時對象this_type()的構造。


總結一下:

和前面介紹的boost::scoped_ptr相比,boost::shared_ptr可以共享對象的所有權,因此其使用範圍基本上沒有什麼限制(還是有一些需要遵循的

使用規則,下文中介紹),自然也可以使用在stl的容器中。另外它還是線程安全的,這點在多線程程序中也非常重要。

boost::shared_ptr並不是絕對安全,下面幾條規則能使我們更加安全的使用boost::shared_ptr:

  1. 避免對shared_ptr所管理的對象的直接內存管理操作,以免造成該對象的重釋放

  2. shared_ptr並不能對循環引用的對象內存自動管理(這點是其它各種引用計數管理內存方式的通病)。

  3. 不要構造一個臨時的shared_ptr作爲函數的參數。

詳見 http://www.boost.org/doc/libs/1_52_0/libs/smart_ptr/shared_ptr.htm

如下列bad 函數內 的代碼則可能導致內存泄漏:
 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
void f(shared_ptr<int>, int);
int g();

void ok()
{
    shared_ptr<int> p(new int(2));
    f(p, g());
}

void bad()
{
    f(shared_ptr<int>(new int(2)), g());
}

如bad 函數內,假設先構造了堆對象,接着執行g(), 在g 函數內拋出了異常,那麼由於裸指針還沒有被智能指針接管,就會出現內存泄漏。


(三)、weak_ptr<T>

如上總結shared_ptr<T> 時說引用計數是一種便利的內存管理機制,但它有一個很大的缺點,那就是不能管理循環引用的對象。

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <boost/shared_ptr.hpp>
#include <iostream>
using namespace std;

class Parent;
class Child;
typedef boost::shared_ptr<Parent> parent_ptr;
typedef boost::shared_ptr<Child> child_ptr;

class Child
{
public:
    Child()
    {
        cout << "Child ..." << endl;
    }
    ~Child()
    {
        cout << "~Child ..." << endl;
    }
    parent_ptr parent_;
};

class Parent
{
public:
    Parent()
    {
        cout << "Parent ..." << endl;
    }
    ~Parent()
    {
        cout << "~Parent ..." << endl;
    }
    child_ptr child_;
};

int main(void)
{
    parent_ptr parent(new Parent);
    child_ptr child(new Child);
    parent->child_ = child;
    child->parent_ = parent;

    return 0;
}

如上述程序的例子,運行程序可以發現Child 和 Parent 構造函數各被調用一次,但析構函數都沒有被調用。由於Parent和Child對象互相引用,


它們的引用計數最後都是1,不能自動釋放,並且此時這兩個對象再無法訪問到。這就引起了內存泄漏

其中一種解決循環引用問題的辦法是 手動打破循環引用,如在return 0; 之前加上一句 parent->child_.reset(); 此時

當棧上智能指針對象child 析構,Child 對象引用計數爲0,析構Chlid 對象,它的成員parent_ 被析構,則Parent 對象引用計數

減爲1,故當棧上智能指針對象parent 析構時,Parent 對象引用計數爲0,被析構。


但手動釋放不僅麻煩而且容易出錯,這裏主要介紹一下弱引用智能指針 weak_ptr<T> 的用法,下面是簡單的定義:
 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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);

        template<class Y>
        weak_ptr &operator=( weak_ptr<Y> && r );

        template<class Y>
        weak_ptr &operator=(shared_ptr<Y> const &r);


        ~weak_ptr();

        bool expired() const;
        shared_ptr<T> lock() const;
    };
}

上面出現了 && 的用法,在這裏並不是邏輯與的意思,而是C++ 11中的新語法,如下解釋:


&& is new in C++11, and it signifies that the function accepts an RValue-Reference -- that is, a reference to an argument that is about 

to be destroyed. 


兩個常用的功能函數:expired()用於檢測所管理的對象是否已經釋放;lock()用於獲取所管理的對象的強引用智能指針。


強引用與弱引用

強引用,只要有一個引用存在,對象就不能釋放

弱引用,並不增加對象的引用計數(實際上是不增加use_count_, 會增加weak_count_);但它能知道對象是否存在

通過weak_ptr訪問對象的成員的時候,要提升爲shared_ptr

如果存在,提升爲shared_ptr(強引用)成功
如果不存在,提升失敗

對於上述的例子,只需要將Parent 類裏面的成員定義改爲如下,即可解決循環引用問題:

 C++ Code 
1
2
3
4
5
class Parent
{
public:
    boost::weak_ptr<parent> child_;
};

因爲此例子涉及到循環引用,而且是類成員引用着另一個類,涉及到兩種智能指針,跟蹤起來難度很大,我也沒什麼心情像分析

shared_ptr 一樣畫多個圖來解釋流程,這個例子需要解釋的代碼遠遠比shared_ptr 多,這裏只是解釋怎樣使用,有興趣的朋友自

己去分析一下。


下面再舉個例子說明lock()  和 expired() 函數的用法:

 C++ Code 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#include <boost/shared_ptr.hpp>
#include <boost/weak_ptr.hpp>
#include <boost/scoped_array.hpp>
#include <boost/scoped_ptr.hpp>
#include <iostream>
using namespace std;

class X
{
public:
    X()
    {
        cout << "X ..." << endl;
    }
    ~X()
    {
        cout << "~X ..." << endl;
    }

    void Fun()
    {
        cout << "Fun ..." << endl;
    }
};
int main(void)
{
    boost::weak_ptr<X> p;
    boost::shared_ptr<X> p3;
    {
        boost::shared_ptr<X> p2(new X);
        cout << p2.use_count() << endl;
        p = p2;
        cout << p2.use_count() << endl;

        /*boost::shared_ptr<X> */
        p3 = p.lock();
        cout << p3.use_count() << endl;
        if (!p3)
            cout << "object is destroyed" << endl;
        else
            p3->Fun();
    }
    /*boost::shared_ptr<X> p4 = p.lock();
    if (!p4)
        cout<<"object is destroyed"<<endl;
    else
        p4->Fun();*/


    if (p.expired())
        cout << "object is destroyed" << endl;
    else
        cout << "object is alived" << endl;

    return 0;
}



從輸出可以看出,當p = p2; 時並未增加use_count_,所以p2.use_count() 還是返回1,而從p 提升爲 p3,增加了

use_count_, p3.use_count() 返回2;出了大括號,p2 被析構,use_count_ 減爲1,程序末尾結束,p3 被析構,

use_count_ 減爲0,X 就被析構了。


參考 :

C++ primer 第四版
Effective C++ 3rd
C++編程規範

http://www.cnblogs.com/TianFang/


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