一、boost 智能指針
智能指針是利用RAII(Resource Acquisition Is Initialization:資源擷取即初始化)來管理資源。關于RAII的讨論可以參考前面的文
章。在使用boost庫之前應該先下載下傳後放在某個路徑,并在VS 包含目錄中添加。下面是boost 庫裡面的智能指針:

(一)、scoped_ptr<T>
先來看例程:
來稍微看一下scoped_ptr 的簡單定義:
與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() 函數時也能夠釋放堆對象,如何實作的呢?
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.
圖示上述程式的過程也就是:
再深入一點,看源碼,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 類的拷貝構造函數設定斷點,然後就可以跟蹤進去,如下的代碼:
故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();
this_type() 構造一個臨時對象,px = 0, pn.pi_ = 0; 然後swap交換p1 與 臨時對象的成員,即現在p1.px = 0; p1.pn.p1_ = 0; 如上圖。
reset 函數傳回,臨時對象需要析構,但跟蹤時卻發現直接傳回了,原因跟上面的一樣,因為shared_ptr 沒有實作析構函數,調用的是預設的析構函
數,與上面拷貝函數同樣的道理,可以在shared_count 類析構函數設定斷點,因為pn 是對象成員,故析構函數也會被調用。如下代碼:
現在use_count_ 減為1,但還不為0,故 dispose(); 和 weak_release(); 兩個函數沒有被調用。當然此時列印 p2.use_count() 就為1 了。
最後 p2.reset(); 跟p1.reset(); 同樣的流程,隻不過現在執行到release 時,use_count_ 減1 為0;需要繼續執行dispose(); 和
weak_release(); 如下代碼:
在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:
避免對shared_ptr所管理的對象的直接記憶體管理操作,以免造成該對象的重釋放
shared_ptr并不能對循環引用的對象記憶體自動管理(這點是其它各種引用計數管理記憶體方式的通病)。
不要構造一個臨時的shared_ptr作為函數的參數。
詳見 http://www.boost.org/doc/libs/1_52_0/libs/smart_ptr/shared_ptr.htm
如下列bad 函數内 的代碼則可能導緻記憶體洩漏:
如bad 函數内,假設先構造了堆對象,接着執行g(), 在g 函數内抛出了異常,那麼由于裸指針還沒有被智能指針接管,就會出現記憶體洩漏。
(三)、weak_ptr<T>
如上總結shared_ptr<T> 時說到引用計數是一種便利的記憶體管理機制,但它有一個很大的缺點,那就是不能管理循環引用的對象。
如上述程式的例子,運作程式可以發現Child 和 Parent 構造函數各被調用一次,但析構函數都沒有被調用。由于Parent和Child對象互相引用,
它們的引用計數最後都是1,不能自動釋放,并且此時這兩個對象再無法通路到。這就引起了記憶體洩漏。
其中一種解決循環引用問題的辦法是 手動打破循環引用,如在return 0; 之前加上一句 parent->child_.reset(); 此時
當棧上智能指針對象child 析構,Child 對象引用計數為0,析構Chlid 對象,它的成員parent_ 被析構,則Parent 對象引用計數
減為1,故當棧上智能指針對象parent 析構時,Parent 對象引用計數為0,被析構。
但手動釋放不僅麻煩而且容易出錯,這裡主要介紹一下弱引用智能指針 weak_ptr<T> 的用法,下面是簡單的定義:
上面出現了 && 的用法,在這裡并不是邏輯與的意思,而是C++ 11中的新文法,如下解釋:
<code>&&</code> 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 類裡面的成員定義改為如下,即可解決循環引用問題:
因為此例子涉及到循環引用,而且是類成員引用着另一個類,涉及到兩種智能指針,跟蹤起來難度很大,我也沒什麼心情像分析
shared_ptr 一樣畫多個圖來解釋流程,這個例子需要解釋的代碼遠遠比shared_ptr 多,這裡隻是解釋怎樣使用,有興趣的朋友自
己去分析一下。
下面再舉個例子說明lock() 和 expired() 函數的用法:
從輸出可以看出,當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/