Boost智能指針使用總結

  內存管理是一個比較繁瑣的問題,C++中有兩個實現方案: 垃圾回收機制智能指針。垃圾回收機制由於性能等緣由不被C++的大佬們推崇, 而智能指針被認爲是解決C++內存問題的最優方案。html

1. 智能指針定義

     一個智能指針就是一個C++的對象, 這對象的行爲像一個指針,可是它卻能夠在其不須要的時候自動刪除。注意這個「其不須要的時候」, 這可不是一個精確的定義。這個不須要的時候能夠指好多方面:局部變量退出函數做用域、類的對象被析構……。因此boost定義了多個不一樣的智能指針來管理不一樣的場景。ios

shared_ptr<T> 內部維護一個引用計數器來判斷此指針是否是須要被釋放。是boost中最經常使用的智能指針了。
scoped_ptr<t> 當這個指針的做用域消失以後自動釋放
intrusive_ptr<T> 也維護一個引用計數器,比shared_ptr有更好的性能。可是要求T本身提供這個計數器。
weak_ptr<T> 弱指針,要和shared_ptr 結合使用
shared_array<T> 和shared_ptr類似,可是訪問的是數組
scoped_array<T> 和scoped_ptr類似,可是訪問的是數組

2. Boost::scoped_ptr<T>

2.1 定義

    scoped_ptr 是boost中最簡單的智能指針。scoped_ptr的目的也是很簡單, 當一個指針離開其做用域時候,釋放相關資源。特別注意的必定就是scoped_ptr 不能共享指針的全部權不能轉移全部權。也就是說這個內存地址就只能給的聲明的變量用,不能給其餘使用。c++

2.2 特色

(1)scoped_ptr的效率和空間的消耗內置的指針差很少程序員

(2)scoped_ptr不能用於管理數組對象,不能指向一塊可以動態增加的內存區域(用scoped_array代替)數組

(3)scoped_ptr不能轉換全部權,所以不能做爲函數的返回值安全

(4)scoped_ptr不能共享全部權,所以不能用於stl的容器中(用shared_ptr代替)多線程

2.3 使用原則

(1)在可能有異常拋出的做用域使用指針ide

(2)函數裏有幾條控制路徑函數

(3)動態分配對象的生存期應被限制於特定的做用域內性能

(4)異常安全很是重要時(總應如此!)

2.4 例子

 1 class test 
 2 { 
 3 public: 
 4     void print() 
 5     { 
 6         cout << "test print now" <<endl; 
 7     } 
 8 };
 9 int _tmain(int argc, _TCHAR* argv[]) 
10 { 
11     boost::scoped_ptr<test> x(new test); 
12     x->print(); 
13     return 0; 
14 }
View Code

3.Boost::shared_ptr<T>

3.1 定義

  boost::shared_ptr是能夠共享全部權的智能指針.

3.2 特色

(1)boost::shared_ptr在內部維護一個引用計數器, 當有一個指針指向這塊內存區域引用計數+1, 反之-1, 若是沒有任何指針指向這塊區域, 引用計數器爲0,釋放內存區域

(2)boost::shared_ptr能夠共享和轉移全部權

(3)boost::shared_ptr能夠被標準庫的容器所使用

(4)boost::shared_ptr是線程安全的,這點在多線程程序中也很是重要

(5)boost::shared_ptr不能指向一塊動態增加的內存(用share_array代替)

3.3 使用原則

(1)避免對shared_ptr所管理的對象的直接內存管理操做,以避免形成該對象的重釋放

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

(3)不要構造一個臨時的shared_ptr做爲函數的參數

3.4 例子

1 int _tmain(int argc, _TCHAR* argv[]) 
2 { 
3     boost::shared_ptr<test> ptr_1(new test); 
4     ptr_1->print();//引用計數爲1
5     boost::shared_ptr<test> ptr_2 = ptr_1; 
6     ptr_2->print();//引用計數爲2
7     ptr_1->print();// 引用計數仍是爲2
8     return 0; 
9 }
View Code

4. Boost::intrusive_ptr<T>

4.1 定義

  boost::intrusive_ptr一種「侵入式」的引用計數指針,它實際並不提供引用計數功能,而是要被存儲的對象本身實現引用計數功能,並提供intrusive_ptr_add_refintrusive_ptr_release函數接口供boost::intrusive_ptr調用。

4.2 使用原則

(1)你須要把 this看成智能指針來使用

(2)已有代碼使用或提供了插入式的引用計數

(3)智能指針的大小必須與裸指針的大小相等

4.3 例子

  下面經過一個具體的例子來講明boost::intrusive_ptr的用法,首先實現一個基類intrusive_ptr_base,定義intrusive_ptr_add_ref和intrusive_ptr_release函數來提供引用計數功能。

 1 /**
 2 * intrusive_ptr_base基類,提供intrusive_ptr_add_ref()和intrusive_ptr_release()函數來提供引用計數功能;
 3 * 使用boost::intrusive_ptr指針存儲的用戶類類型必須繼承自intrusive_ptr_base基類。
 4 */
 5 #include <ostream>
 6 #include <boost/checked_delete.hpp>
 7 #include <boost/detail/atomic_count.hpp>
 8  
 9  
10 template<class T>
11 class intrusive_ptr_base {
12 public:
13     /**
14     * 缺省構造函數
15     */
16     intrusive_ptr_base(): ref_count(0) {
17         std::cout << "  Default constructor " << std::endl;
18     }
19      
20     /**
21     * 不容許拷貝構造,只能使用intrusive_ptr來構造另外一個intrusive_ptr
22     */
23     intrusive_ptr_base(intrusive_ptr_base<T> const&): ref_count(0) {
24         std::cout << "  Copy constructor..." << std::endl;
25     }
26      
27     /**
28     * 不容許進行賦值操做
29     */
30     intrusive_ptr_base& operator=(intrusive_ptr_base const& rhs) {
31         std::cout << "  Assignment operator..." << std::endl;
32         return *this;
33     }
34      
35     /**
36     * 遞增引用計數(放到基類中以便compiler能找到,不然須要放到boost名字空間中)
37     */
38     friend void intrusive_ptr_add_ref(intrusive_ptr_base<T> const* s) {
39         std::cout << "  intrusive_ptr_add_ref..." << std::endl;
40         assert(s->ref_count >= 0);
41         assert(s != 0);
42         ++s->ref_count;
43     }
44  
45     /**
46     * 遞減引用計數
47     */
48     friend void intrusive_ptr_release(intrusive_ptr_base<T> const* s) {
49         std::cout << "  intrusive_ptr_release..." << std::endl;
50         assert(s->ref_count > 0);
51         assert(s != 0);
52         if (--s->ref_count == 0)
53             boost::checked_delete(static_cast<T const*>(s));  //s的實際類型就是T,intrusive_ptr_base<T>爲基類
54     }
55      
56     /**
57     * 相似於shared_from_this()函數
58     */
59     boost::intrusive_ptr<T> self() {
60         return boost::intrusive_ptr<T>((T*)this);
61     }
62      
63     boost::intrusive_ptr<const T> self() const {
64         return boost::intrusive_ptr<const T>((T const*)this);
65     }
66      
67     int refcount() const {
68         return ref_count;
69     }
70      
71 private:
72     ///should be modifiable even from const intrusive_ptr objects
73     mutable boost::detail::atomic_count ref_count;
74  
75 };
View Code

  用戶類類型須要繼承intrusive_ptr_base基類,以便具備引用計數功能。

 1 #include <iostream>
 2 #include <string>
 3 #include <boost/intrusive_ptr.hpp>
 4 #include "intrusive_ptr_base.hpp"
 5  
 6 /**
 7 * 用戶類類型繼承自intrusive_ptr_base,該實現方式相似於boost::enable_shared_from_this<Y>
 8 */
 9 class Connection : public intrusive_ptr_base< Connection > {
10 public:
11     /**
12     * 構造函數,調用intrusive_ptr_base< Connection >的缺省構造函數來初始化對象的基類部分
13     */
14     Connection(int id, std::string tag):
15         connection_id( id ), connection_tag( tag ) {}
16  
17     /**
18     * 拷貝構造函數,只複製自身數據,不能複製引用計數部分
19     */
20     Connection(const Connection& rhs):
21         connection_id( rhs.connection_id ), connection_tag( rhs.connection_tag) {}
22      
23     /**
24     * 賦值操做,一樣不能複製引用計數部分
25     */
26     const Connection operator=( const Connection& rhs) {
27         if (this != &rhs) {
28             connection_id = rhs.connection_id;
29             connection_tag = rhs.connection_tag;
30         }
31          
32         return *this;
33     }
34  
35 private:
36     int connection_id;
37     std::string connection_tag;
38 };
39  
40 int main() {
41     std::cout << "Create an intrusive ptr" << std::endl;
42     boost::intrusive_ptr< Connection > con0 (new Connection(4, "sss") );  //調用intrusive_ptr_add_ref()遞增引用計數
43     std::cout << "Create an intrusive ptr. Refcount = " << con0->refcount() << std::endl;
44  
45     boost::intrusive_ptr< Connection > con1 (con0);   //調用intrusive_ptr_add_ref()
46     std::cout << "Create an intrusive ptr. Refcount = " << con1->refcount() << std::endl;
47     boost::intrusive_ptr< Connection > con2 = con0;   //調用intrusive_ptr_add_ref()
48     std::cout << "Create an intrusive ptr. Refcount = " << con2->refcount() << std::endl;
49      
50     std::cout << "Destroy an intrusive ptr" << std::endl;
51  
52     return 0;
53 }
View Code

 

程序運行輸出:
Create an intrusive ptr
Default constructor 
intrusive_ptr_add_ref...
Create an intrusive ptr. Refcount = 1
intrusive_ptr_add_ref...
Create an intrusive ptr. Refcount = 2
intrusive_ptr_add_ref...
Create an intrusive ptr. Refcount = 3
Destroy an intrusive ptr
intrusive_ptr_release...
intrusive_ptr_release...
intrusive_ptr_release...

4.4 boost::intrusive_ptr與boost::shared_ptr區別

  使用boost::shared_ptr用戶類自己不須要具備引用計數功能,而是由boost::shared_ptr來提供;使用boost::shared_ptr的一大陷阱就是用一個raw pointer屢次建立boost::shared_ptr,這將致使boost::shared_ptr析構時該raw pointer被屢次銷燬當。即不能以下使用:

1 int *a = new int(5);
2 boost::shared_ptr ptr1(a);
3 boost::shared_ptr ptr2(a);  //錯誤!
View Code

  boost::intrusive_ptr徹底具有boost::shared_ptr的功能,且不存在shared_ptr的問題,便可以利用raw pointer建立多個intrusive _ptr,其緣由就在於引用計數的ref_count對象shared_ptr是放在shared_ptr結構裏,而目標對象T經過繼承intrusive_ptr_base引用計數做爲T對象內部成員變量,就不會出現一個對象有兩個引用計數器的狀況出現。
  那麼爲何一般鼓勵你們使用shared_ptr,而不是intrusive_ptr呢, 在於shared_ptr不是侵入性的,能夠指向任意類型的對象; 而intrusive_ptr所要指向的對象,須要繼承intrusive_ptr_base,即便不須要引用計數成員也會被建立。

4.5 結論

  若是建立新類且須要進行傳遞,則繼承intrusive_ptr_base使用intrusive_ptr

5. Boost::weak_ptr<T>

5.1 定義

    weak_ptr 就是一個弱指針。weak_ptr 被shared_ptr控制, 它能夠經過share_ptr的構造函數或者lock成員函數轉化爲share_ptr

5.2 特色

(1)weak_ptr的一個最大特色就是它共享一個share_ptr的內存

(2)不管是構造仍是析構一個weak_ptr都不會影響引用計數器

5.3 弱引用與強引用

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

 1 boost::weak_ptr
 2 
 3 boost::weak_ptr<T>是boost提供的一個弱引用的智能指針,它的聲明能夠簡化以下:
 4 
 5 namespace boost {
 6 
 7     template<typename T> class weak_ptr {
 8     public:
 9         template <typename Y>
10         weak_ptr(const shared_ptr<Y>& r);
11 
12         weak_ptr(const weak_ptr& r);
13 
14         ~weak_ptr();
15 
16         T* get() const; 
17         bool expired() const; 
18         shared_ptr<T> lock() const;
19     }; 
20 }
View Code

  能夠看到,boost::weak_ptr必須從一個boost::share_ptr或另外一個boost::weak_ptr轉換而來,這也說明,進行該對象的內存管理的是那個強引用的boost::share_ptrboost::weak_ptr只是提供了對管理對象的一個訪問手段

  boost::weak_ptr除了對所管理對象基本訪問功能(經過get()函數)外,還有兩個經常使用的功能函數:expired()用於檢測所管理的對象是否已經釋放lock()用於獲取所管理的對象的強引用指針

5.4 循環引用

5.4.1 循環定義

  引用計數是一種便利的內存管理機制,但它有一個很大的缺點,那就是不能管理循環引用的對象

5.4.2 循環引用例子

 1 #include <string>
 2 #include <iostream>
 3 #include <boost/shared_ptr.hpp>
 4 #include <boost/weak_ptr.hpp>
 5 
 6 class parent;
 7 class children;
 8 
 9 typedef boost::shared_ptr<parent> parent_ptr;
10 typedef boost::shared_ptr<children> 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 
31 void test()
32 {
33     parent_ptr father(new parent());
34     children_ptr son(new children);
35 
36     father->children = son;
37     son->parent = father;
38 }
39 
40 void main()
41 {
42     std::cout<<"begin test...\n";
43     test();
44     std::cout<<"end test.\n";
45 }
View Code

  運行該程序能夠看到,即便退出了test函數後,因爲parent和children對象互相引用,它們的引用計數都是1,不能自動釋放,而且此時這兩個對象再沒法訪問到。這就引發了c++中那臭名昭著的內存泄漏。

通常來說,解除這種循環引用有下面有三種可行的方法

(1)當只剩下最後一個引用的時候須要手動打破循環引用釋放對象

(2)當parent的生存期超過children的生存期的時候,children改成使用一個普通指針指向parent

(3)使用弱引用的智能指針打破這種循環引用

雖然這三種方法均可行,但方法1和方法2都須要程序員手動控制,麻煩且容易出錯。這裏主要介紹一下第三種方法和boost中的弱引用的智能指針boost::weak_ptr。

5.4.3 經過boost::weak_ptr來打破循環引用

  因爲弱引用不更改引用計數,相似普通指針,只要把循環引用的一方使用弱引用,便可解除循環引用。對於上面的那個例子來講,只要把children的定義改成以下方式,便可解除循環引用:

1 class children
2 {
3 public:
4     ~children() { std::cout <<"destroying children\n"; }
5 
6 public:
7     boost::weak_ptr<parent> parent;
8 };
View Code

  最後值得一提的是,雖然經過弱引用指針能夠有效的解除循環引用,但這種方式必須在程序員能預見會出現循環引用的狀況下才能使用,也能夠是說這個僅僅是一種編譯期的解決方案,若是程序在運行過程當中出現了循環引用,仍是會形成內存泄漏的。所以,不要認爲只要使用了智能指針便能杜絕內存泄漏。畢竟,對於C++來講,因爲沒有垃圾回收機制,內存泄漏對每個程序員來講都是一個很是頭痛的問題。

5.5 使用原則

(1)要打破遞歸的依賴關係

(2)使用一個共享的資源不須要共享全部權

(3)避免懸空的指針

(4)shared_ptr構造weak_ptr時,weak_ptr所指內存爲空會拋出異常,而weak_ptr的lock()成員不會拋出異常但會返回個空指針,根據本身需求選擇

5.6 例子

 1 int _tmain(int argc, _TCHAR* argv[]) 
 2 { 
 3     boost::shared_ptr<test> sharePtr(new test);;
 4     boost::weak_ptr<test> weakPtr(sharePtr); 
 5     //weakPtr 就是用來保存指向這塊內存區域的指針的 
 6     //幹了一大堆其餘事情
 7     boost::shared_ptr<test> sharePtr_2 = weakPtr.lock(); 
 8     if (sharePtr_2) 
 9         sharePtr_2->print();
10     return 0; 
11 }
View Code

6. Boost::shared_array<T> 和Boost::scoped_array<T>

6.1 定義

    前面提到過shared_ptr和scoped_ptr不能用於數組的內存(new []),因此shared_array和scoped_array就是他們的代替品。

6.2 例子

1 int _tmain(int argc, _TCHAR* argv[]) 
2 { 
3     const int size = 10; 
4     boost::shared_array<test> a(new test[]);
5     for (int i = 0; i < size; ++i) 
6         a[i].print();
7     return 0; 
8 }
View Code

7.std::auto_ptr

7.1 定義

  auto_ptr是C++標準庫裏的類,它接受一個類型形參的模板,爲動態分配的對象提供異常安全。其實,它的核心思想是:用一個對象存儲須要被自動釋放的資源,而後依靠對象的析構函數來釋放資源

7.2 特色

(1)auto_ptr的構造函數帶explicit 關鍵字,必須使用初始化的直接形式建立auto_ptr對象。        

1 auto_ptr<int> ap(new int(1024));  //ok   
2 auto_ptr<int> ap=new int(1024);   //error
View Code

(2)auto_ptr 在析構函數中釋放了動態分配的空間,所以能自動釋放內存。下面函數只動態分配了內存,並無顯示釋放。可是編譯器保證在展開棧越過f以前運行pi的析構函數。

1 void f() { auto_ptr<int> ap(new int(1024)); } 
View Code

(3)auto_ptr重載瞭解引用操做符箭頭操做符,支持了普通指針的行爲。

(4)賦值時刪除了左操做數指向的對象

1 auto_ptr<int> ap1(new int(1024));  auto_ptr<int> ap2;
2 ap2=ap1;
View Code

       將ap1賦值給ap2後,刪除了ap2原來指的對象;ap2置爲指向ap1所指的對象;ap1爲未綁定對象。可看代碼。

(5)測試auto_ptr對象,能夠調用get成員函數,該函數返回包含在auto_ptr對象中的基礎指針。

1 if(ap.get())  *ap=512;   //ok          
2 if(ap) *ap=512;             //error
View Code

7.3 使用原則

儘管auto_ptr類模板爲處理動態分配的內存提供了安全性和便利性的尺度,可是也存在很多缺陷,接下來結合例子給出auto_ptr的一些缺陷。

(1)不要使用auto_ptr對象保存指向靜態分配對象的指針。不然,當auto_ptr對象自己被撤銷時,它將試圖刪除指向非動態分配對象的指針,致使未定義的行爲。

1 int a=1;
2 auto_ptr<int> ap(&a);  //編譯沒有問題,會致使未定義行爲
View Code

(2)不要使兩個auto_ptr對象指向同一對象

1 auto_ptr<int> ap1(new int (1024));
2 auto_ptr<int> ap2(ap1.get());
View Code

(3)不要使用auto_ptr對象保存指向動態分配數組的指針。從源代碼中能夠看出,它用的是delete操做符,而不是delete [ ] 操做符

(4)不要auto_ptr對象存儲在容器中。由於auto_ptr的複製賦值具備破壞性。不知足容器要求:複製或賦值後,兩個對象必須具備相同值

7.4 例子

 1 #include <utility>
 2 #include <iostream>
 3 using namespace std;
 4  
 5 class A
 6 {
 7 public:
 8     A() { id = ++count; cout << "create A" << id  <<  "\n"; }
 9     ~A() { cout << "destroy A" << id << "\n"; }
10 private:
11     static int count;
12     int id;
13 };
14  
15 int A::count = 0;
16  
17 /*調用該函數會丟失掉全部權*/
18 void sink(auto_ptr<A> a)
19 {
20     cout << "Enter sink()\n";
21 }
22  
23 /*調用該函數會建立對象,並獲取全部權*/
24 auto_ptr<A> create()
25 {
26     cout << "Enter create()\n";
27     auto_ptr<A> a(new A());
28     return a;
29 }
30  
31 int main(int argc, char *argv[])
32 {
33     auto_ptr<A> a1 = create();
34     auto_ptr<A> a2 = a1; /*轉移全部權,此時a1無效了*/
35     auto_ptr<A> a3(new A());
36     cout << "Exit create()\n";
37     sink(a2);/*丟失全部權,會發現a2的釋放在sink函數中進行*/
38     cout << "Exit sink()\n";
39     return 0;
40 }
41  
42 輸出結果是:<br>Enter create()<br>create A1<br>create A2<br>Exit create()<br>Enter sink()<br>destroy A1<br>Exit sink()<br>destroy A2<br><br>
View Code

8. 使用智能指針的幾個注意點

(1)聲明一個智能指針的時候要當即給它實例化, 並且必定不能手動釋放它。

(2)…_ptr<T> 不是T* 類型。因此:

          a: 聲明的時候要…_ptr<T> 而不是….._ptr<T*>。

          b:不能把T* 型的指針賦值給它。

          c: 不能寫ptr=NULL, 而用ptr.reset()代替。

(3)不能循環引用

(4)不要聲明臨時的share_ptr, 而後把這個指針傳遞給一個函數

 

原文連接:http://www.cnblogs.com/sld666666/archive/2010/12/16/1908265.html

相關文章
相關標籤/搜索