C++中的auto_ptr智能指針

C++中的auto_ptr(俗稱智能指針)所做的事情,使用起來就像普通指針,但當其動態分配內存時,不再需要考慮清理問題。當它的生存期結束時,系統會自動清理它指向的內存。

 

其實auto_ptr是一個模版類(注意實質上還是一個類)。主要解決內存泄漏問題。

 

原理:其實就是RAII,在構造的時候獲取資源,在析構的時候釋放資源,並進行相關指針操作的重載,使用起來就像普通的指針(!!!其實實質上還是一個類)。

 

0. 先看看如何使用:

  1. int * a=new int(5);  
  2. auto_ptr<int> ap(a);//獲取某個對象的所有權  
  3. cout<<(*ap)<<endl;//之後ap就可以像指針一樣使用了。  

注意頭文件是#include<memory>

注意是auto<int>而不是auto<int *>。觀察源代碼發現類auto_ptr的私有成員是T* ap;所以這裏是<int>而不是<int*>。

 

最重要的就是理解源碼:

  1. template<class T>  
  2. class auto_ptr  
  3. {  
  4. private:  
  5. T*ap;  
  6. public:  
  7. //constructor&destructor-----------------------------------(1)  
  8. explicit auto_ptr(T*ptr=0)throw():ap(ptr){}//首先它的構造函數是不支持隱式轉換的  
  9. ~auto_ptr()throw()//雖然構造函數中沒有new空間,但是這裏使用了delete。是因爲智能指針是用來獲取指針所有權的,而這個指針是指向new出來的空間的。所以析構需要delete  
  10. {  
  11. delete ap;  
  12. }  
  13. //Copy--------------------------------------------(2)  
  14. //它的copy有兩個函數,注意兩個copy函數內部實現,源智能指針將失去對指針的所有權。  
  15. auto_ptr(auto_ptr &rhs)throw():ap(rhs.release())//同類型的copy,例如:auto_ptr<int> a(p); auto_ptr<int> b(a);-->就是調用了這個copy  
  16. {}                                                
  17. template<class Y>  
  18. auto_ptr(auto_ptr<Y>& rhs)throw():ap(rhs.release())//模版中的類型不同的copy。例如:auto_ptr<Derived> a(p); auto_ptr<Base> b(a);用於多態中  
  19. {}  
  20. //assignment  
  21. //和copy類似,源智能指針將失去對指針的所有權。  
  22. auto_ptr& operator=(auto_ptr& rhs)throw()  
  23. {  
  24. reset(rhs.release());  
  25. return*this;  
  26. }  
  27. template<class Y>  
  28. auto_ptr& operator=(auto_ptr<Y>& rhs)throw()//參照上面的copy,用於多態中。  
  29. {  
  30. reset(rhs.release());  
  31. return*this;  
  32. }  
  33. //Dereference----------------------------------------------------(3)  
  34. //類auto_ptr重載了這兩個操作符,使得它用起來像指針一樣。  
  35. T&operator*()const throw()  
  36. {  
  37. return*ap;  
  38. }  
  39. T*operator->()const throw()  
  40. {  
  41. return ap;  
  42. }  
  43. //Helperfunctions------------------------------------------------(4)  
  44. //類auto_ptr實現了幾個公共函數,提供用戶使用  
  45. //valueaccess  
  46. T* get()const throw()//獲取該智能指針擁有的指針  
  47. {  
  48. return ap;  
  49. }  
  50. //release ownership  
  51. T* release()throw()//釋放該智能指針擁有的指針  
  52. {  
  53. T*tmp(ap);  
  54. ap=0;  
  55. return tmp;  
  56. }  
  57. //reset value  
  58. void reset(T*ptr=0)throw()//給該智能指針重新獲取另一個指針的所有權  
  59. {  
  60. if(ap!=ptr)  
  61. {  
  62. delete ap;  
  63. ap=ptr;  
  64. }  
  65. }  
  66. //Special conversions-----------------------------------------------(5)  
  67. //存在的作用是可以使得下列的式子成立。  
  68. //auto_ptr<int> ap=auto_ptr<int>(new int(1));  
  69. //auto_ptr<int> ap;  
  70. //ap=auto_ptr<int> (new int(1));  
  71. template<class Y>  
  72. struct auto_ptr_ref  
  73. {  
  74. Y* yp;  
  75. auto_ptr_ref(Y*rhs):yp(rhs){}  
  76. };  
  77.   
  78. auto_ptr(auto_ptr_ref<T> rhs)throw():ap(rhs.yp)  
  79. {}  
  80. auto_ptr& operator=(auto_ptr_ref<T> rhs)throw()  
  81. {  
  82. reset(rhs.yp);  
  83. return*this;  
  84. }  
  85. template<class Y>  
  86. operator auto_ptr_ref<Y>()throw()  
  87. {  
  88. return auto_ptr_ref<Y>(release());  
  89. }  
  90. template<class Y>  
  91. operator auto_ptr<Y>()throw()  
  92. {  
  93. return auto_ptr<Y>(release());  
  94. }  
  95. };  


注意的方面:

1. 看看構造函數與析構函數

1> auto的構造時獲得對某個對象的所有權。

  1. class A  
  2. {  
  3. public: A(int i):m_a(i)  
  4.               {}  
  5.         int m_a;  
  6. };  
  7. int _tmain(int argc, _TCHAR* argv[])  
  8. {  
  9.        int * a=new int(5);  
  10.        A*a=new A(3);  
  11.        auto_ptr<int> ap(a);  
  12.        //auto_ptr<int> ap=a;   -----(1) error  
  13.        auto_ptr<A>apA=auto_ptr<A>(new A(2));  
  14.        //auto_ptr<A> apA=new A(2);  ------(2)         error  
  15.        //auto_ptr<A> apA=(auto_ptr<A>)a; ------(3)    error  
  16.        system("pause");  
  17.        return 0;  
  18. }  

注意:

        前兩個錯誤在於調用了類auto_ptr的copy構造,而很顯然沒有匹配的copy構造。其實就相當於兩個不相干的類的對象進行copy構造,這樣顯然是不對的。

         3錯誤在於調用了類的賦值重載,其實就相當於兩個不相干的類的對象進行賦值操作,這樣顯然很荒謬。

2>  因爲auto_ptr析構會刪除內存。所以不要兩個auto_ptr擁有同一對象:

  1. int * a=new int(5);  
  2. auto_ptr<int> ap1(a);  
  3. auto_ptr<int> ap2(a);  

這樣是很危險。

3> 因爲auto_ptr的析構中刪除指針用的是delete,而不是delete[].所以我們不能用auto_ptr來管理一個類對象數組。

 

2. 拷貝和賦值函數

1> 由源碼可知,auto_ptr的拷貝和賦值後,源智能指針將失去對指針的所有權。

  1. int * p=new int(2);  
  2. auto_ptr<int> a(p);  
  3. auto_ptr<int> b(a);  
  4. //cout<<*a<<endl;    error  

2> 由於拷貝和賦值的特殊性,當智能指針作爲參數對函數進行傳參時,就會出現問題,傳參的過程中,系統會自動生成一個臨時智能指針,實參對其copy構造,則實參就失去了所有權,而函數結束後,系統會銷燬這個臨時智能指針,所以連同綁定的指針指向的內存都已經被釋放,顯然不合理。

所以不要把智能指針當作參數。

3> 有源碼中可以看到copy和賦值都有兩個函數,而第二個函數就是用於多態的。用子類指針對父類指針進行copy構造和賦值。

4> auto_ptr不能作爲STL中的容器對象。因爲STL容器中的元素要經常copy和賦值,而auto_ptr會傳遞所有權,不是值語義的。所以不行。

附:值語義就相當於值傳遞;對象語義就相當於引用傳遞。

 3. auto_ptr提供了一些函數可供使用

get();  release();reset();

 4. 說一下特殊轉換

//auto_ptr<int> ap=auto_ptr<int>(new int(1));

//auto_ptr<int> ap;

//ap=auto_ptr<int> (new int(1));

如果想使得這兩個式子成立,必須要有auto_ptr_ref。

首先:

//auto_ptr<int> ap=auto_ptr<int>(new int(1));

因爲auto_ptr<int>(newint(1))是一個臨時對象,而臨時對象是不能作爲copy構造裏的引用參數的,所以我們必須重寫一個copy構造,使得copy構造的參數不是引用類型。

而我們不能寫出這樣的copy:auto_ptr(auto_ptr p){};這樣顯然是不行的,因爲傳參就會調用copy,則它就會循環調用。所以我們寫成:

  1. auto_ptr(auto_ptr_ref<T> rhs)throw():ap(rhs.yp)  
  2. {}  

之後,我們就要把auto_ptr可以轉換成auto_ptr_ref,所以我們寫了一個類型轉換函數:

  1. template<class Y>  
  2. operatorauto_ptr_ref<Y>()throw()  
  3. {  
  4. returnauto_ptr_ref<Y>(release());  
  5. }  

這樣,我們就可以把那個臨時對象轉換成auto_ptr_ref,然後再調用我們重寫的這個copy構造,我們就實現了這個copy過程。

 

 

//auto_ptr<int> ap;

//ap=auto_ptr<int> (new int(1));

而賦值過程和上面的過程是類似的。我們重寫賦值函數就可以了。

至此我們用掉用了3個函數。

而auto_ptr_ref裏面有4個函數。還有一個:

  1. template<class Y>  
  2. operatorauto_ptr<Y>()throw()  
  3. {  
  4. returnauto_ptr<Y>(release());  
  5. }  
  6. };  

而這個類型轉換函數的作用:就是不同模版類型可以相互轉換。比如:

  1. template<typename T>  
  2. class Base  
  3. {  
  4. public:  
  5.        template<typenameY>  
  6.        operator Base<Y>(){  
  7.               return Base<Y>();  
  8.        }  
  9. };  
  10.    
  11. int _tmain(int argc, _TCHAR* argv[])  
  12. {  
  13.        Base<int> a;  
  14.        Base<short> b;  
  15.        a=b;//OK  
  16.        system("pause");  
  17.        return 0;  
  18. }  

使得a=b;可行。但是具體在auto_ptr中,這個函數具體怎麼用,還沒有遇到這種情況。

 

心得:

其實這個auto_ptr就是一個類,只是重載了*和->符號,所以它的對象可以像指針一樣:*p來返回引用對象。p->來調用對象的成員。需要注意的是類auto_ptr的實現(重點),比如copy和賦值都跟常理不同。而因爲本質上是一個類,所以銷燬它是就自動調用了析構函數,使得不會因爲異常而內存泄漏。就是RAII技術。

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