學習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智能指針循環引用問題一句話概括就是:要釋放的堆對象被該堆對象自己內部的智能指針成員變量增加引用計數阻止了。