简体   繁体   English

shared_ptr 的循环依赖问题是什么?

[英]What is the cyclic dependency issue with shared_ptr?

I read about shared pointers and understood how to use.我阅读了共享指针并了解了如何使用。 But I never understood the cyclic dependency problem with shared pointers and how weak pointers are going to fix those issues.但我一直不明白共享指针的循环依赖问题以及弱指针将如何解决这些问题。 Can any one please explain this problem clearly?任何人都可以清楚地解释这个问题吗?

The problem isn't that complex.问题没那么复杂。 Let --> represent a shared pointer:-->代表一个共享指针:

The rest of the program  --> object A --> object B
                                    ^     |
                                     \    |
                                      \   v
                                        object C

So we've got ourselves a circular dependency with shared pointers.所以我们已经有了一个共享指针的循环依赖。 What's the reference count of each object?每个对象的引用计数是多少?

A:  2
B:  1
C:  1

Now suppose the rest of the program (or at any rate the part of it that holds a shared pointer to A) is destroyed.现在假设程序的其余部分(或者至少是其中包含指向 A 的共享指针的部分)被销毁。 Then the refcount of A is reduced by 1, so the reference count of each object in the cycle is 1. So what gets deleted?那么 A 的 refcount 减 1,所以循环中每个对象的引用计数都是 1。那么删除的是什么? Nothing.没有什么。 But what do we want to be deleted?但是我们要删除什么? Everything, because none of our objects can be reached from the rest of the program any more.一切,因为我们的任何对象都无法从程序的其余部分访问。

So the fix in this case is to change the link from C to A into a weak pointer.因此,在这种情况下,解决方法是将 C 到 A 的链接更改为弱指针。 A weak pointer doesn't affect the reference count of its target, which means that when the rest of the program releases A, its refcount hits 0. So it's deleted, hence so is B, hence so is C.弱指针不会影响其目标的引用计数,这意味着当程序的其余部分释放 A 时,它的引用计数为 0。因此它被删除,因此 B 也是如此,因此 C 也是如此。

Before the rest of the program releases A, though, C can access A whenever it likes by locking the weak pointer.但是,在程序的其余部分释放 A 之前,C 可以通过锁定弱指针随时访问 A。 This promotes it to a shared pointer (and increases the refcount of A to 2) for as long as C is actively doing stuff with A. That means if A is otherwise released while this is going on then its refcount only falls to 1. The code in C that uses A doesn't crash, and A is deleted whenever that short-term shared pointer is destroyed.只要 C 正在积极地对 A 做任何事情,这就会将它提升为共享指针(并将 A 的引用计数增加到 2)。这意味着如果 A 在此过程中以其他方式被释放,那么它的引用计数只会下降到 1。 C 中使用 A 的代码不会崩溃,只要该短期共享指针被销毁,A 就会被删除。 Which is at the end of the block of code that locked the weak pointer.这是锁定弱指针的代码块的末尾。

In general, deciding where to put the weak pointers might be complex.通常,决定将弱指针放在哪里可能很复杂。 You need some kind of asymmetry among the objects in the cycle in order to choose the place to break it.您需要循环中的对象之间存在某种不对称性,以便选择打破它的地方。 In this case we know that A is the object referred to by the rest of the program, so we know that the place to break the cycle is whatever points to A.在这种情况下,我们知道 A 是程序其余部分引用的对象,因此我们知道打破循环的地方是指向 A 的任何地方。

shard_ptr<A> <----| shared_ptr<B> <------
    ^             |          ^          |
    |             |          |          |
    |             |          |          |
    |             |          |          |
    |             |          |          |
class A           |     class B         |
    |             |          |          |
    |             ------------          |
    |                                   |
    -------------------------------------

Now if we make the shared_ptr of the class B and A, the use_count of the both pointer is two.现在如果我们制作B类和A类的shared_ptr,两个指针的use_count都是2。

When the shared_ptr goes out od scope the count still remains 1 and hence the A and B object does not gets deleted.当 shared_ptr 超出范围时,计数仍然保持为 1,因此 A 和 B 对象不会被删除。

class B;

class A
{
    shared_ptr<B> sP1; // use weak_ptr instead to avoid CD

public:
    A() {  cout << "A()" << endl; }
    ~A() { cout << "~A()" << endl; }

    void setShared(shared_ptr<B>& p)
    {
        sP1 = p;
    }
};

class B
{
    shared_ptr<A> sP1;

public:
    B() {  cout << "B()" << endl; }
    ~B() { cout << "~B()" << endl; }

    void setShared(shared_ptr<A>& p)
    {
        sP1 = p;
    }
};

int main()
{
    shared_ptr<A> aPtr(new A);
    shared_ptr<B> bPtr(new B);

    aPtr->setShared(bPtr);
    bPtr->setShared(aPtr);

    return 0;  
}

output:输出:

A()
B()

As we can see from the output that A and B pointer are never deleted and hence memory leak.正如我们从输出中看到的,A 和 B 指针永远不会被删除,因此内存泄漏。

To avoid such issue just use weak_ptr in class A instead of shared_ptr which makes more sense.为避免此类问题,只需在 A 类中使用 weak_ptr 而不是更有意义的 shared_ptr 。

If you are aware of the cyclic dependency then you can stick with shared_ptr without switching to weak_ptr but the deletion of objects require some manual work.如果您知道循环依赖性,那么您可以坚持使用shared_ptr而无需切换到weak_ptr ,但是删除对象需要一些手动工作。 The following code is modified from Swapnil's answer.以下代码是根据 Swapnil 的回答修改的。

#include <iostream>
#include <memory>

using namespace std ;

class B;

class A
{
   shared_ptr<B> sP1; // use weak_ptr instead to avoid CD

public:
   A() {  cout << "A()" << endl; }
   ~A() { cout << "~A()" << endl; }

   void setShared(shared_ptr<B>& p)
   {
       sP1 = p;
   }

   // nullifySharedPtr cuts the circle of reference
   // once this is triggered, then the ice can be broken
   void nullifySharedPtr() {
      sP1 = nullptr; 
   }

};

class B
{
   shared_ptr<A> sP1;

public:
   B() {  cout << "B()" << endl; }
   ~B() { cout << "~B()" << endl; }

   void setShared(shared_ptr<A>& p)
   {
       sP1 = p;
   }
};

int main()
{
   shared_ptr<A> aPtr(new A);
   shared_ptr<B> bPtr(new B);
   
   aPtr->setShared(bPtr);
   bPtr->setShared(aPtr);

   cout << aPtr.use_count() << endl;
   cout << bPtr.use_count() << endl;

   // to be break the ice:
   aPtr->nullifySharedPtr() ;
   
   return 0;  
}

nullifySharedPtr acts as a scissor to cut the circle and hence enables the system to do its own job of deletion. nullifySharedPtr充当剪刀剪圆,因此使系统能够完成自己的删除工作。

The Issue itself is displayed above.问题本身显示在上面。 Solutions are解决方案是

  • Manual breaking according to roy.atlas根据 roy.atlas 手动破解
  • Assert by design, that you have a tree structure, without cycles, eg in a XML-DOM tree, you would put the parent relation as weak pointer通过设计断言,你有一个树结构,没有循环,例如在 XML-DOM 树中,你会把父关系作为弱指针
  • In a generic solution an object will exist, as long as there is a chain of shared_ptr-s from a local or static variable, ie from any object, which is not on the heap.在通用解决方案中,只要存在来自本地或 static 变量的 shared_ptr-s 链,即来自不在堆上的任何 object,就会存在 object。 For this you need to be able to detect the memory segment type of an object, which depends on the runtime environment;为此,您需要能够检测到 object 的 memory 段类型,这取决于运行时环境; also each shared_ptr-member of a class must know the surrounding instance. class 的每个 shared_ptr 成员也必须知道周围的实例。 Now, when a shared_ptr is deleted, the referenced object can find an alternative path from a non-heap object or it will be destroyed.现在,当删除 shared_ptr 时,引用的 object 可以从非堆 object 找到替代路径,否则它将被销毁。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM