Share_ptr 智能指針

Share_ptr也是一種智能指針。類比於auto_ptr學習。所以推薦先學習auto_ptr,再來學習shared_ptr。本博客的前兩個就是auto_ptr的總結。希望感興趣的朋友可以看看。

 

Shared_ptr和auto_ptr最大的區別就是,shared_ptr解決了指針間共享對象所有權的問題,也就是auto_ptr中的賦值的奇怪問題。所以滿足了容器的要求,可以用於容器中。而auto_ptr顯然禁止共享對象所有權,不可以用於容器中。

  1. int * a=new int(2);  
  2. shared_ptr<int> sp1(a);  
  3. shared_ptr<int> sp2(sp1);     OK  

當然shared_ptr作爲一種智能指針,也擁有和shared_ptr一些相似的性質。它們本質上都是類,但是使用起來像指針。它們都是爲了解決防止內存泄漏的解決方案。都是運用了RAII技術來實現的。

 

注意:使用shared_ptr也要引用頭文件#include<memory>

 

由於shared_ptr的源碼過於複雜,我們不給出源碼。類比於auto_ptr學習.

 

1. 首先類shared_ptr有兩個成員變量。T * px和unsign long * pn;

T * px;顯然和auto_ptr一樣,用於儲存對象的指針。

pn用於記錄有多少個shared_ptr擁有同一個對象。pn是shared_ptr對象間共享的,類似於static成員變量。 

  1. template<class T>  
  2. class shared_ptr{  
  3. private:  
  4.        T *px; // contained pointer  
  5.     unsignedlong* pn; // reference counter  
  6. }  

總結:其實shared_ptr的原理,就是使用px來記錄指針,使用*pn來記錄px指向的對象的擁有者share_ptr的個數,當一個shared_ptr對象達到作用域時,不會釋放資源,只有當*pn變爲0的時候,纔會釋放指針指向的資源。

 

2. 一個簡單實現的源碼(仍然看懂源碼還是最重要的。

  1. #pragma once  
  2. //shared_ptr的簡單實現版本  
  3. //基於引用記數的智能指針  
  4. //它可以和stl容器完美的配合  
  5. namespace boost  
  6. {  
  7. template<class T>  
  8. class shared_ptr  
  9. {  
  10. typedef unsigned longsize_type;  
  11. private:  
  12.        T *px; // contained pointer  
  13.    size_type* pn; // reference counter  
  14. public:  
  15. //構造函數---------------------------------------------------2  
  16. /* 
  17. int* a=new int(2); 
  18. shared_ptr<int> sp; 
  19. shared_ptr<int> sp(a); 
  20. */  
  21. explicitshared_ptr(T* p=0) : px(p)  
  22. {  
  23.    pn = new size_type(1);  
  24. }  
  25.    
  26. /* 
  27. Derived d; 
  28. shared_ptr<Base> ap(d); 
  29. */  
  30. template<typename Y>  
  31. shared_ptr(Y* py)  
  32. {  
  33. pn = newsize_type(1);  
  34. px=py;  
  35. }  
  36. //copy構造函數------------------------------------------------  
  37. /* 
  38. int * a=new int; 
  39. shared_ptr<int> sp(a); 
  40. shared_ptr<int> sp1(sp); 
  41. */  
  42. shared_ptr(constshared_ptr& r) throw(): px(r.px)  
  43. {  
  44. ++*r.pn;  
  45. pn = r.pn;  
  46. }  
  47.    
  48. /* 
  49. shared_ptr<Derived>sp1(derived); 
  50. shared_ptr<Base> sp2(sp1); 
  51. */  
  52. template<typename Y>  
  53. shared_ptr(constshared_ptr<Y>& r)//用於多態  
  54. {  
  55. px = r.px;  
  56. ++*r.pn;  
  57. pn = r.pn; //shared_count::op= doesn't throw  
  58. }  
  59. //重載賦值operator=--------------------------------------------  
  60. shared_ptr& operator=(const shared_ptr& r) throw()  
  61. {  
  62. if(this== &r) return *this;  
  63. dispose();  
  64. px = r.px;  
  65. ++*r.pn;  
  66. pn = r.pn;  
  67. return *this;  
  68. }  
  69. template<typename Y>  
  70. shared_ptr& operator=(const shared_ptr<Y>& r)//用於多態  
  71. {  
  72. dispose();  
  73. px = r.px;  
  74. ++*r.pn;  
  75. pn = r.pn; //shared_count::op= doesn't throw  
  76. return *this;  
  77. }  
  78.    
  79. ~shared_ptr() { dispose(); }  
  80. void reset(T* p=0)  
  81. {  
  82. if ( px == p ) return;  
  83. if (--*pn == 0)  
  84. delete(px); }  
  85. else  
  86. // allocate newreference  
  87. // counter  
  88. // fix: prevent leak if new throws  
  89. try { pn = new size_type; }  
  90. catch (...) {  
  91. // undo effect of —*pn above to  
  92. // meet effects guarantee  
  93. ++*pn;  
  94. delete(p);  
  95. throw;  
  96. // catch  
  97. // allocate newreference counter  
  98. *pn = 1;  
  99. px = p;  
  100. // reset  
  101. reference operator*()const throw(){ return *px; }  
  102. pointer operator->()const throw(){ return px; }  
  103. pointer get() constthrow(){ returnpx; }  
  104. size_type use_count() constthrow()//  
  105. return *pn; }  
  106. bool unique() const throw()//  
  107. return *pn ==1; }  
  108. private:  
  109. void dispose() throw()  
  110. {  
  111. if (--*pn == 0)  
  112. delete px; delete pn; }  
  113. }  
  114. }; // shared_ptr  
  115. template<typename A,typenameB>  
  116. inline bool operator==(shared_ptr<A>const & l, shared_ptr<B> const & r)  
  117. {  
  118. return l.get() == r.get();  
  119. }  
  120. template<typename A,typenameB>  
  121. inline bool operator!=(shared_ptr<A>const & l, shared_ptr<B> const & r)  
  122. {  
  123. return l.get() != r.get();  
  124. }  
  125. }//namespace boost  


要注意的地方:

 

3. Shared_ptr和auto_ptr都有類似的規定:

看看它們的copy構造和重載賦值都可以看出:

不允許

  1. int* a=new int(2);  
  2. shared_ptr<int>sp=a;//  error  
  3. sp=a;//    error  

就是不允許使用一個純指針給一個智能指針賦值或copy構造。只能使用智能指針給另一個智能指針賦值或copy構造。

  1.    int* a=new int(2);  
  2. hared_ptr<int> sp(a);//構造函數  
  3. shared_ptr<int> sp1(sp);//copy構造  
  4.    sp1=sp;//賦值  

在auto_ptr中也是相同的。

 

4. 注意shared_ptr的幾個函數

Ø     Reset()函數:重置函數

標準中的是:

  1.    int* a=new int(2);  
  2.     int* b=new int(3);  
  3.  shared_ptr<int> sp2(a);  
  4.  shared_ptr<int> sp1(a);  
  5. shared_ptr<int> sp(a);  
  6.     sp.reset(b);  
  7.    sp.reset();  
  8.    sp.reset(sp2);  -----!!!也是可以的。  

使得sp獲得b的擁有權。失去a的擁有權。注意這會使得a的擁有者少1.當a的擁有者變爲0時,就會釋放a的資源。

Ø     Swap()函數:交換函數

  1. int* a=new int(2);  
  2. shared_ptr<int> sp(a);  
  3. shared_ptr<int> sp1(a);  
  4. sp.swap(sp1);  

就是兩個shared_ptr中的px和pn都互換一下。

Ø     Get()函數:返回px

Ø     Use_count函數:返回*pn,就是對象的擁有者的數量。

Ø     Unique函數:令*pn=1;讓對象的擁有者的數量變爲1。返回bool

Ø     同時share_ptr也重載了*和->

 

5. tr1中重載了幾個有關shared_ptr的符號:

template<classT, class U>

booloperator==(shared_ptr<T> const& a, shared_ptr<U> const& b);

判斷擁有的對象是否是一樣的

 

template<classT, class U>

 bool operator!=(shared_ptr<T> const&a, shared_ptr<U> const& b);

判斷擁有的對象是否是不一樣的

 

template<classT, class U>

 bool operator<(shared_ptr<T>const& a, shared_ptr<U> const& b);

重載了小於號,在STL中的LIST中非常有用。

  1. int* a=new int(2);  
  2. int* b=new int(3);  
  3. shared_ptr<int> sp(a);  
  4. shared_ptr<int> sp1(b);  
  5. if(sp<sp1)  
  6.        cout<<"2222"<<endl;  

6. 注意真實中shared_ptr中沒有public dispose這個函數,這裏只是爲了避免代碼重複。

 

7. 注意shared_ptr中的析構函數中不是直接釋放資源,而是調用了dispose函數,如果*pn==0了,纔會釋放資源。

 

8.shared_ptr的多線程的安全性

shared_ptr 本身不是 100%線程安全的。它的引用計數本身是安全且無鎖的,但對象的讀寫則不是,因爲shared_ptr有兩個數據成員,讀寫操作不能原子化。根據文檔shared_ptr的線程安全級別和內建類型、標準庫容器、string一樣,即:

  • 一個 shared_ptr 實體可被多個線程同時讀取;
  • 兩個的 shared_ptr 實體可以被兩個線程同時寫入,“析構”算寫操作;
  • 如果要從多個線程讀寫同一個 shared_ptr 對象,那麼需要加鎖。

 

發現了兩個非常有意思的東西:

1. 看tr1中的源碼中發現兩個這樣的東西:

template<class Y, classD> shared_ptr(Y * p, D d);

template<class Y, classD> void reset(Y * p, D d);

其中的D d是個什麼東西?源碼的解釋是d是一個deleter(刪除器)。至此我們突然發現我們可以給shared_ptr指定一個刪除器,當*pn==0的時候,不去釋放資源,而去調用我們自己給它的刪除器。

 

當shared_ptr的引用次數爲0的時候,share_ptr就會調用釋放函數來釋放資源。

當我們希望引用次數爲0的時候,shared_ptr不釋放資源,而是調用我們指定的操作的時候,就會用到D d;

  1. void foo(int * d)  
  2. {  
  3.        cout<<"1234"<<endl;  
  4. }  
  5.    
  6. int _tmain(int argc, _TCHAR* argv[])  
  7. {  
  8.        int* a=new int(2);  
  9.        shared_ptr<int> sp(a,foo);  
  10.        shared_ptr<int> sp1(sp);  
  11.        sp.reset();  
  12.        sp1.reset();  
  13.        //_CrtDumpMemoryLeaks();  
  14.        system("pause");  
  15.        return 0;  
  16. }  


注意!:

1. 指定的刪除器的參數必須是int*;和shared_ptr<int>中的int對應。不能是其他的,或者爲空也是錯的。因爲系統會把shared_ptr的對象px賦給刪除器的參數,我們也可以在刪除器中釋放資源。

2. 只有a的引用次數爲0纔會調用,所以如果沒有sp1.reset()。也不會調用foo函數。

 

2. 使用shared_ptr的時候,要小心,想一想操作的內在含義纔去做。

1>

  1. int* a=new int(2);  
  2. shared_ptr<int> sp(a);  
  3. shared_ptr<int> sp1(sp);  
  4. sp.reset();//--------(1)  
  5. sp.reset();//--------(2)  


這裏(1)是重置了sp,注意(2)是沒有任何作用的,不能使得a的引用次數變爲0.想一想reset的函數內部,(2)的時候,sp中的對象pn已經爲空了,則不能改變*pn的值了。

 

2>

  1. int* a=new int(2);  
  2.   shared_ptr<int> sp(a);//----------(1)  
  3.   shared_ptr<int> sp1(a);//---------(2)  

注意:這裏的(2)也是不對的。想一想shared_ptr的構造函數,(1)的時候,sp的px指向a,且*pn爲1.而(2)的時候,px指向a,且*pn也是1.這顯然就問題了。a被引用了2次,但是*pn爲1.在最後作用域達到的時候,就會釋放2次內存,這就會引發異常。

 

總結:shared_ptr和auto_ptr的區別。

Shared_ptr有兩個變量,一個記錄對象地址,一個記錄引用次數

Auto_ptr只有一個變量,用來記錄對象地址

 

Shared_ptr可用多個shared_ptr擁有一個資源。

Auto_ptr只能一個auto_ptr擁有一個資源

 

Shared_ptr可以實現賦值的正常操作,使得兩個地址指向同一資源

Auto_ptr的賦值很奇怪,源失去資源擁有權,目標獲取資源擁有權

 

Shared_ptr到達作用域時,不一定會釋放資源。

Auto_ptr到達作用於時,一定會釋放資源。

 

Shared_ptr存在多線程的安全性問題,而auto_ptr沒有。

 

Shared_ptr可用於容器中,而auto_ptr一般不可以用於容器中。


Shared_ptr可以在構造函數、reset函數的時候允許指定刪除器。而auto_ptr不能。

 

還有這裏說一句:使用智能指針(不管shared_ptr還是auto_ptr),都要清除源碼內部的實現原理,使用起來纔不會錯。而且使用的時候,一定要想一想函數內部的實現原理再去使用。切記小心。

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