1. 程式人生 > 實用技巧 >shared_ptr智慧指標為什麼迴圈引用會出問題

shared_ptr智慧指標為什麼迴圈引用會出問題

  學習C++的shared_ptr智慧指標你可能會碰到一個問題,迴圈引用為什麼會出現問題?為什麼不能釋放?C++不是保證了物件構造成功退出作用域時就絕對會呼叫解構函式嗎,呼叫解構函式不也會呼叫成員變數和父類的解構函式嗎,為什麼還不能釋放呢?難道是編譯器有bug?

  非也,原因是一句繞口令式的答案:你以為的不是你以為的。

  為什麼?先看看下面的迴圈引用程式碼示例:

 1 #include <iostream>
 2 #include <typeinfo>
 3 
 4 using namespace std;
 5 
 6 template <typename T>
 7
class SharedPointer { 8 private: 9 class Implement { 10 public: 11 Implement(T* p) : mPointer(p), mRefs(1) { 12 cout << "Implement()" << endl; 13 } 14 15 ~Implement(){ 16 delete mPointer; 17 cout << "~Implement()
" << endl; 18 } 19 20 T* mPointer; 21 size_t mRefs; 22 }; 23 24 Implement* mImplPtr; 25 26 public: 27 explicit SharedPointer(T* p = nullptr) 28 : mImplPtr(new Implement(p)) { 29 cout << "SharedPointer<" << typeid(T).name() << "
>(" << this << ")" << endl; 30 } 31 32 ~SharedPointer() { 33 cout << "~SharedPointer<" << typeid(T).name() << ">(" << this << ")" << endl; 34 decrease(); 35 } 36 37 SharedPointer(const SharedPointer& other) 38 : mImplPtr(other.mImplPtr) { 39 increase(); 40 cout << "SharedPointer<" << typeid(T).name() << ">(other=" << &other << ")" << endl; 41 } 42 43 SharedPointer& operator = (const SharedPointer& other) { 44 if(mImplPtr != other.mImplPtr) { 45 decrease(); 46 mImplPtr = other.mImplPtr; 47 increase(); 48 } 49 50 return *this; 51 } 52 53 T* operator -> () const { 54 return mImplPtr->mPointer; 55 } 56 57 T& operator * () const { 58 return *(mImplPtr->mPointer); 59 } 60 61 private: 62 void decrease() { 63 if(--(mImplPtr->mRefs) == 0) { 64 delete mImplPtr; 65 } 66 } 67 68 void increase() { 69 ++(mImplPtr->mRefs); 70 } 71 }; 72 73 class B; 74 75 class A { 76 public: 77 SharedPointer<B> m_ptr; 78 }; 79 80 class B { 81 public: 82 SharedPointer<A> m_ptr; 83 }; 84 85 int main() { 86 SharedPointer<A> a(new A); 87 SharedPointer<B> b(new B); 88 a->m_ptr = b; 89 b->m_ptr = a; 90 91 return 0; 92 }

  執行程式碼,你會得到下方的結果(記憶體地址可能不同):

 1 Implement()
 2 SharedPointer<1B>(0x417eb0)
 3 Implement()
 4 SharedPointer<1A>(0x7fff4fd10230)
 5 Implement()
 6 SharedPointer<1A>(0x418f20)
 7 Implement()
 8 SharedPointer<1B>(0x7fff4fd10218)
 9 ~Implement()
10 ~Implement()
11 ~SharedPointer<1B>(0x7fff4fd10218)
12 ~SharedPointer<1A>(0x7fff4fd10230)

  為什麼申請的兩個堆空間沒有被釋放??

  原因是析構智慧指標物件時所呼叫的解構函式發現引用計數仍然不為0,故而不能釋放。

  為什麼引用計數仍然不為0,因為我們的迴圈引用導致了引用計數額外各增加了1,而解構函式並不知情,也無法知情,所以無法修正,也不應該去修正,因為這不是解構函式該乾的活。

  那我們反過來推理,如果要釋放兩個堆空間如何操作?

  要釋放兩個棧空間就必須保證智慧指標物件析構時一併釋放它們,也就是引用計數最終為0,然而要讓引用計數最終為0就需要兩個堆空間的成員變數m_ptr智慧指標先析構,但是堆空間的成員變數只能在堆空間析構時才能析構,這就進入雞生蛋蛋生雞的問題了。。。所以誰也不能先析構。

  總之,兩個堆空間沒有釋放是因為指向它們的智慧指標成員變數沒有析構導致引用計數不為0,這個智慧指標成員變數沒有析構又是因為它們所屬的堆物件沒有析構,而這兩個堆物件沒有析構是因為它們被智慧指標保管,該智慧指標又被指向的堆物件的智慧指標成員變數增加了引用計數。

  

  解決的辦法就是用weak_ptr取代智慧指標成員變數,從而解決shared_ptr智慧指標迴圈引用的問題。

  shared_ptr智慧指標迴圈引用問題一句話概括就是:要釋放的堆物件被該堆物件自己內部的智慧指標成員變數增加引用計數阻止了。