shared_ptr智能指针为什么循环引用会出问题

Posted duacai

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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智能指针循环引用问题一句话概括就是:要释放的堆对象被该堆对象自己内部的智能指针成员变量增加引用计数阻止了。

以上是关于shared_ptr智能指针为什么循环引用会出问题的主要内容,如果未能解决你的问题,请参考以下文章

C++智能指针shared_ptr 定位删除器(仿函数)

智能指针的模拟实现shared_ptr 循环引用 定置删除器

深入学习c++--智能指针 weak_ptr(打破shared_ptr循环引用)

智能指针的循环引用与解决

C++:shared_ptr

C++智能指针