1. 程式人生 > 實用技巧 >C++智慧指標的原理和實現

C++智慧指標的原理和實現

一、智慧指標起因

  在C++中,動態記憶體的管理是由程式設計師自己申請和釋放的,用一對運算子完成:new和delete。

  new:在動態記憶體中為物件分配一塊空間並返回一個指向該物件的指標;

  delete:指向一個動態獨享的指標,銷燬物件,並釋放與之關聯的記憶體。

  使用堆記憶體是非常頻繁的操作,容易造成堆記憶體洩露、二次釋放等問題,為了更加容易和更加安全的使用動態記憶體,C++11中引入了智慧指標的概念,方便管理堆記憶體,使得自動、異常安全的物件生存期管理可行。智慧指標主要思想是RAII思想,“使用物件管理資源”,在類的建構函式中獲取資源,在類的解構函式中釋放資源。智慧指標的行為類似常規指標,重要的區別是它負責自動釋放所指向的物件。

  RAII是Resource Acquisition Is Initialization的簡稱,即資源獲取就是初始化:

  1.定義一個類來封裝資源的分配與釋放;

  2.建構函式中完成資源的分配及初始化;

  3.解構函式中完成資源的清理,可以保證資源的正確初始化和釋放;

  4.如果物件是用宣告的方式在棧上建立區域性物件,那麼RAII機制就會正常工作,當離開作用域物件會自動銷燬而呼叫解構函式釋放資源。

二、智慧指標型別

  智慧指標在C++11版本之後提供,包含在標頭檔案<memory>中,標準命名std空間下,有auto_ptr、shared_ptr、weak_ptr、unique_ptr四種,其中auto_ptr已被棄用。

  auto_ptr:擁有嚴格物件所有權語義的智慧指標;

  shared_ptr:擁有共享物件所有權語義的智慧指標;

  weak_ptr:到shared_ptr所管理物件的弱引用;

  unique_ptr:擁有獨有物件所有權語義的智慧指標。

2.1auto_ptr

  auto_ptr是通過由new 表示式獲得的物件,並在auto_ptr自身被銷燬時刪除該物件的智慧指標,它可用於為動態分配的物件提供異常安全、傳遞動態分配物件的所有權給函式和從函式返回動態分配的物件,是一個輕量級的智慧指標,適合用來管理生命週期比較短或者不會被遠距離傳遞的動態物件,最好是侷限於某個函式內部或者是某個類的內部。

  宣告:

  template<classT>classauto_ptr;

  template<>classauto_ptr<void>; // 對型別void特化  

  成員函式:

  (1)get: 獲得內部物件的指標;

  (2)release:釋放被管理物件的所有權,將內部指標置為空,返回內部物件的指標,此指標需要手動釋放;

  (3)reset:銷燬內部物件並接受新的物件的所有權;

  (4)operator=:從另一auto_ptr轉移所有權;

  (5)operator*operator->:訪問被管理物件。

  注意事項:

  (1) 其建構函式被宣告為explicit,因此不能使用賦值運算子對其賦值,即不能使用類似這樣的形式 auto_ptr<int> p = new int;

  (2)auto_ptr 的物件所有權是獨佔性的,使用拷貝構造和賦值操作符時,會造成物件所有權的轉移,被拷貝物件在拷貝過程中被修改;

  (3) 基於第二條,因此不能將auto_ptr放入到標準容器中或作為容器的成員;

  (4)auto_ptr不能指向陣列,釋放時無法確定是陣列指標還是普通指標;

  (5) 不能把一個原生指標交給兩個智慧指標物件管理,對其它智慧指標也是如此。

  auto_ptr是最早期的智慧指標,C++11 中已被棄用,C++17 中移除,建議使用unique_ptr代替auto_ptr。

  簡單實現:

 1 template<class T>
 2 class AutoPointer
 3 {
 4 public:
 5     AutoPointer(T* ptr)
 6       :mPointer(ptr){}
 7 
 8     AutoPointer(AutoPointer<T>& other)
 9     {
10         mPointer= other.mPointer;  //管理權進行轉移
11         other.mPointer= NULL;
12     }
13 
14     AutoPointer& operator = (AutoPointer<T>& other)
15     {
16         if(this != &other)
17         {
18             delete mPointer;
19             mPointer = other.mPointer;  //管理權進行轉移
20             other.mPointer= NULL;
21         }
22 
23         return *this;
24     }
25 
26     ~AutoPointer()
27     {
28         delete mPointer;
29     }
30 
31     T& operator * ()
32     {
33         return *mPointer;
34     }
35 
36     T* operator -> ()
37     {
38         return mPointer;
39     }
40 
41 private:
42 
43     T* mPointer;
44 };

2.2 shared_ptr

  shared_ptr多個指標指向相同的物件,也叫共享指標。shared_ptr採用了引用計數的方式,更好地解決了賦值與拷貝的問題,每一個shared_ptr的拷貝都指向相同的記憶體,每拷貝一次內部的引用計數加1,每析構一次內部的引用計數減1,為0時自動刪除所指向的堆記憶體。shared_ptr內部的引用計數是執行緒安全的,但是物件的讀取時需要加鎖。

宣告:

  template<classT>classshared_ptr; 

  成員函式:

  (1)get: 獲得內部物件的指標;

  (2)swap:交換所管理的物件;

  (3)reset:替換所管理的物件;

  (4)use_count:返回shared_ptr所指物件的引用計數;

  (5)operator*operator->:解引用儲存的物件指標;

  (6)operator=:對shared_ptr賦值;

  (7)operator bool:檢查是否有關聯的管理物件;

  (8)owner_before:提供基於擁有者的共享指標排序。

  交換:std::swap(std::shared_ptr)特化的swap演算法用於交換兩個智慧指標。

  初始化:通過建構函式傳入指標初始化,也可以使用std::make_sharedstd::allocate_shared函式初始化。

  注意事項:

  (1) 不能將指標直接賦值給一個智慧指標,一個是類,一個是指標。不能使用類似這樣的形式 shared_ptr<int> p = new int;

  (2) 避免迴圈引用,這是shared_ptr的一個最大陷阱,導致記憶體洩漏,這一點在weak_ptr中將得到完善;

  (3)管理陣列指標時,需要制定Deleter以使用delete[]操作符銷燬記憶體,shared_ptr並沒有針對陣列的特化版本;

  (4) 不能把一個原生指標交給兩個智慧指標物件管理,對其它智慧指標也是如此。

  簡單實現:

 1 template <typename T>
 2 class SharedPointer
 3 {
 4 private:
 5     
 6     class Implement
 7     {
 8     public:
 9         Implement(T* p) : mPointer(p), mRefs(1){}
10         ~Implement(){ delete mPointer;}
11         
12         T* mPointer;  //實際指標
13         size_t mRefs;  // 引用計數
14     };
15     
16     Implement* mImplPtr;
17     
18 public:
19     
20     explicit SharedPointer(T* p)
21       : mImplPtr(new Implement(p)){}
22         
23     ~SharedPointer()
24     {
25         decrease();  // 計數遞減
26     }
27     
28     SharedPointer(const SharedPointer& other)
29       : mImplPtr(other.mImplPtr)
30     {
31         increase();  // 計數遞增
32     }
33     
34     SharedPointer& operator = (const SharedPointer& other)
35     {
36         if(mImplPtr != other.mImplPtr)  // 避免自賦值
37         {
38             decrease();
39             mImplPtr = other.mImplPtr;
40             increase();
41         }
42         
43         return *this;
44     }
45     
46     T* operator -> () const
47     {
48         return mImplPtr->mPointer;
49     }
50     
51     T& operator * () const
52     {
53         return *(mImplPtr->mPointer);
54     }
55     
56 private:
57     
58     void decrease()
59     {
60         if(--(mImplPtr->mRefs) == 0)
61         {
62             delete mImplPtr;
63         }
64     }
65     
66     void increase()
67     {
68         ++(mImplPtr->mRefs);
69     }
70 };

2.3 weak_ptr

  weak_ptr是為了配合shared_ptr而引入的一種智慧指標,用於專門解決shared_ptr迴圈引用的問題,因為它不具有普通指標的行為,沒有過載operator * 和 ->,它的最大作用在於協助shared_ptr工作,像旁觀者那樣觀測資源的使用情況。weak_ptr可以從一個shared_ptr或者另一個weak_ptr物件構造,獲得資源的觀測權。但weak_ptr沒有共享資源,它的構造不會引起指標引用計數的增加。weak_ptr可以使用一個非常重要的成員函式lock(),從被觀測的shared_ptr獲得一個可用的shared_ptr物件,從而操作資源。

  宣告:

  template<classT>class weak_ptr; 

  成員函式:

  (1)swap:交換所管理的物件;

  (2)reset:替換所管理的物件;

  (3)use_count:返回shared_ptr所指物件的引用計數;

  (4)operator=:對shared_ptr賦值;

  (5)expired:檢查被引用的物件是否已刪除;

  (6)owner_before:提供基於擁有者的共享指標排序;

  (7)lock:建立管理被引用的物件的shared_ptr。

  交換:std::swap(std::weak_ptr)特化的swap演算法用於交換兩個智慧指標。

  注意事項:

  (1) 不能將指標直接賦值給一個智慧指標,一個是類,一個是指標。不能使用類似這樣的形式 shared_ptr<int> p = new int;

  (2) 不能把一個原生指標交給兩個智慧指標物件管理,對其它智慧指標也是如此。

  簡單實現:weak_ptr的典型實現儲存二個指標,即指向控制塊的指標和作為構造來源的shared_ptr的儲存指標。

  以下是VC的原始碼實現:

 1 template<class _Ty>
 2 class weak_ptr
 3     : public _Ptr_base<_Ty>
 4 {    // class for pointer to reference counted resource
 5     typedef typename _Ptr_base<_Ty>::_Elem _Elem;
 6 
 7 public:
 8     weak_ptr()
 9     {    // construct empty weak_ptr object
10     }
11 
12     template<class _Ty2>
13     weak_ptr(const shared_ptr<_Ty2>& _Other,
14         typename enable_if<is_convertible<_Ty2 *, _Ty *>::value,
15         void *>::type * = 0)
16     {    // construct weak_ptr object for resource owned by _Other
17         this->_Resetw(_Other);
18     }
19 
20     weak_ptr(const weak_ptr& _Other)
21     {    // construct weak_ptr object for resource pointed to by _Other
22         this->_Resetw(_Other);
23     }
24 
25     template<class _Ty2>
26     weak_ptr(const weak_ptr<_Ty2>& _Other,
27         typename enable_if<is_convertible<_Ty2 *, _Ty *>::value,
28         void *>::type * = 0)
29     {    // construct weak_ptr object for resource pointed to by _Other
30         this->_Resetw(_Other);
31     }
32 
33     ~weak_ptr()
34     {    // release resource
35         this->_Decwref();
36     }
37 
38     weak_ptr& operator=(const weak_ptr& _Right)
39     {    // assign from _Right
40         this->_Resetw(_Right);
41         return (*this);
42     }
43 
44     template<class _Ty2>
45     weak_ptr& operator=(const weak_ptr<_Ty2>& _Right)
46     {    // assign from _Right
47         this->_Resetw(_Right);
48         return (*this);
49     }
50 
51     template<class _Ty2>
52     weak_ptr& operator=(shared_ptr<_Ty2>& _Right)
53     {    // assign from _Right
54         this->_Resetw(_Right);
55         return (*this);
56     }
57 
58     void reset()
59     {    // release resource, convert to null weak_ptr object
60         this->_Resetw();
61     }
62 
63     void swap(weak_ptr& _Other)
64     {    // swap pointers
65         this->_Swap(_Other);
66     }
67 
68     bool expired() const
69     {    // return true if resource no longer exists
70         return (this->_Expired());
71     }
72 
73     shared_ptr<_Ty> lock() const
74     {    // convert to shared_ptr
75         return (shared_ptr<_Elem>(*this, false));
76     }
77 };

2.4 unique_ptr

  unique_ptr實際上相當於一個安全性增強了的auto_ptr。unique_ptr是通過指標佔有並管理另一物件,並在unique_ptr離開作用域時釋放該物件的智慧指標。unique_ptr的使用標誌著控制權的轉移,同一時刻只能有一個unique_ptr指向給定物件,通過禁止拷貝語義、只有移動語義來實現。相比與原始指標unique_ptr用於其RAII的特性,使得在出現異常的情況下,動態資源能得到釋放。

  宣告:

  template<classT,classDeleter=std::default_delete<T>>classunique_ptr;

  template<classT,classDeleter>classunique_ptr<T[], Deleter>; // 管理陣列指標

  成員函式:

  (1)get: 返回指向被管理物件的指標;

  (2)get_deleter:返回用於析構被管理物件7的刪除器;

  (3)swap:交換所管理的物件;

  (4)reset:替換所管理的物件;

  (5)release:返回一個指向被管理物件的指標,並釋放所有權;

  (6)operator bool:檢查是否有關聯的被管理物件;

  (7)operator=:為unique_ptr賦值;

  (8)operator*operator->:解引用儲存的物件指標。

  注意事項:

  (1) 不能將指標直接賦值給一個智慧指標,一個是類,一個是指標。不能使用類似這樣的形式 shared_ptr<int> p = new int;

  (2) 不能把一個原生指標交給兩個智慧指標物件管理,對其它智慧指標也是如此。

  簡單實現:

  1 //default deleter for unique_ptr
  2 template<typename T>
  3 struct DefaultDeleter
  4 {
  5     void operator () (T *p)
  6     {
  7         if(p)
  8         {
  9             delete p;
 10             p = NULL;
 11         }
 12     }
 13 };
 14 
 15 template<typename T, typename Deleter = DefaultDeleter<T>>
 16 class unique_ptr
 17 {
 18 public:
 19 
 20     // construct 
 21     unique_ptr(T *pT = NULL);
 22 
 23     // destroy
 24     ~unique_ptr();
 25 
 26 private:
 27 
 28     // not allow copyable
 29     unique_ptr(const unique_ptr &);
 30 
 31     unique_ptr&operator=(const unique_ptr &);
 32 
 33 public:
 34 
 35     // reset 
 36     void reset(T *p);
 37 
 38     // release the own of the pointer
 39     T* release();
 40 
 41     // get the pointer
 42     T* get();
 43 
 44     // convert unique_ptr to bool
 45     operator bool() const;
 46 
 47     // overload for operator *
 48     T& operator * ();
 49 
 50     // overload for operator ->
 51     T* operator -> ();
 52 
 53 private:
 54 
 55     T *m_pT;  //pointer
 56     
 57     Deleter m_deleter;  //deleter
 58     
 59     void del();  //call deleter
 60 };
 61 
 62 
 63 template<typename T, typename Deleter>
 64  unique_ptr<T, Deleter>::unique_ptr(T *pT) :m_pT(pT)
 65 {
 66 
 67 }
 68 
 69 template<typename T, typename Deleter>
 70  unique_ptr<T, Deleter>::~unique_ptr()
 71 {
 72     del();
 73 }
 74 
 75 template<typename T, typename Deleter>
 76  void unique_ptr<T, Deleter>::del()
 77 {
 78     if(*this)
 79     {
 80         m_deleter(m_pT);
 81         m_pT = NULL;
 82     }
 83 }
 84 
 85 template<typename T, typename Deleter>
 86  T* unique_ptr<T, Deleter>::get()
 87 {
 88     return m_pT;
 89 }
 90 
 91 template<typename T, typename Deleter>
 92  void unique_ptr<T, Deleter>::reset(T *p)
 93 {
 94     del();
 95     m_pT = p;
 96 }
 97 
 98 template<typename T, typename Deleter>
 99  T* unique_ptr<T, Deleter>::release()
100 {
101     T *p = m_pT;
102     m_pT = NULL;
103     return p;
104 }
105 
106 template<typename T, typename Deleter>
107  unique_ptr<T, Deleter>::operator bool() const
108 {
109     return NULL != m_pT;
110 }
111 
112 template<typename T, typename Deleter>
113  T& unique_ptr<T, Deleter>::operator * ()
114 { 
115     return *m_pT;
116 }
117 
118 template<typename T, typename Deleter>
119  T* unique_ptr<T, Deleter>::operator -> ()
120 {
121     return m_pT;
122 }

三、總結

  智慧指標就是模擬指標動作的類,一般智慧指標都會過載 -> 和 * 操作符。智慧指標主要作用是管理動態記憶體的釋放。

  1.不要使用std::auto_ptr;

  2.當你需要一個獨佔資源所有權的指標,且不允許任何外界訪問,請使用std::unique_ptr;

  3.當你需要一個共享資源所有權的指標,請使用std::shared_ptr;

  4.當你需要一個能訪問資源,但不控制其生命週期的指標,請使用std::weak_ptr;

  5.不能把一個原生指標交給兩個智慧指標物件管理。