2

在文件啊:

class B;
class A
{
public:
    B *b;
    A(B *b = nullptr)
    {
        this->b = b;
    }
    ~A()
    {
        delete this->b;
    }
};

在文件 bh 中:

class A;
class B   
{
public:
    A *a;
    B(A *a = nullptr)
    {
        this->a = a;
    }
    ~B()
    {
        delete this->a;
    };
};

假设我们有一个指向 A *object 的指针,我们想删除它:

// ...
A *a = new A();
B *b = new B();
A->b = b;
B->a = a;
// ...

delete a;

// ...

A 的解构器会说删除 B;即调用B的解构器。B 的解构器会说 delete A.death loop lé infinitè。

有没有更好的方法来编写代码来解决这个问题?这不是紧迫的问题,只是好奇。

谢谢!

4

5 回答 5

5

使用智能指针 (ie std::shared_ptr) 和弱指针 (ie std::weak_ptr) 而不是纯指针。

您必须定义哪个类真正拥有哪个类。如果没有人拥有另一个,那么两者都应该是弱指针。

于 2012-11-29T02:46:28.557 回答
3

此代码具有未定义的行为。第一次delete a发生,然后A::~A调用delete b. 然后B::~B调用delete a,这导致双重释放。

在这种情况下,您的实现可以做任何它想做的事情,包括无限循环,或者只是“似乎工作”。您必须确保对象只被delete编辑一次。

对于这个特定的应用程序,您可能需要考虑拥有A“自己的” B,反之亦然。这样, egA总是负责调用delete B。这在您有一些对象树的情况下很常见,其中孩子可以引用父母,反之亦然。在这种情况下,父母“拥有”孩子并负责删除孩子。

shared_ptr如果你不能这样做,你也许可以用and想出一些解决方案weak_ptr(这是循环的,所以shared_ptr单独对你没有帮助);但是您应该强烈考虑设计这样的情况不会出现。

在这个具体的例子中,类A不拥有指向的指针B——main拥有指向的指针B。因此,析构函数代码A可能不应该删除任何东西——应该由main(或者更确切地说,希望std::unique_ptr是 中的实例main)处理。

于 2012-11-29T02:45:42.643 回答
2

这是循环数据依赖的问题,而不是循环析构函数依赖的问题。如果指针链a->b->a->b->...->a...最终指向 a NULL,则析构函数将终止;如果指针的轨迹回到开头,您将在两次删除同一个对象时得到未定义的行为。

这个问题和删除一个循环链表没有太大区别:一不小心就可以回来一整圈。您可以使用通常称为龟兔赛跑的常用技术来检测结构中的A/B循环,并NULL在触发析构函数链之前将“反向指针”设置为。

于 2012-11-29T02:46:41.663 回答
1

首先,你这里的设计很糟糕。如果AandB是相互关联的,并且取出 anA会破坏Bit 链接的,反之亦然,则必须有一种方法可以将该关系重构为父子关系。 A如果可能,应该是“父级” B,因此破坏 aB不会破坏A它链接到的,但破坏 anA会破坏B它包含的。

当然,事情并不总是这样,有时你会有这种不可避免的循环依赖。为了让它工作(直到你有机会重构!)打破破坏循环

struct A
{
  B* b;
  ~A(){
    if( b ) {
      b->a = 0 ; // don't "boomerang" and let ~B call my dtor again
      delete b ;
    }
  }
} ;

struct B
{
  A* a;
  ~B(){
    if( a ) {
      a->b = 0 ; // don't "boomerang" and let ~A call my dtor again
      delete a ;
    }
  }
} ;

请注意,这仅适用于AB指向彼此的场景

在此处输入图像描述

不适用于这种情况

在此处输入图像描述

上面的情况是一个循环链表,你必须向前看,this->b->a->b直到你this再次到达,将最终指针设置为 NULL,然后开始销毁过程。A除非从一个公共基类B继承,否则这将很难做到。

于 2013-04-30T16:13:04.973 回答
1

这是一个简单的解决方案:通过在删除对象之前将该字段设置为 null 来打破任何循环。

编辑:这通常不起作用,例如以下仍然会崩溃:

A* a1 = new A();
B* b1 = new B();
A* a2 = new A();
B* b2 = new B();

a1->b = b1;
b1->a = a2;
a2->b = b2;
b2->a = a1;

delete a1;

-

class A
{
    ...
    ~A()
    {
        // does b points to us?

        if(this->b && this->b->a == this)
        {
            this->b->a = nullptr;

            // b no longer points to us
        }

        // can safely delete b now

        delete this->b;
    }
};

...

class B
{
    ...
    ~B()
    {
        // does a point to us?

        if(this->a && this->a->b == this)
        {
            this->a->b = nullptr;

            // a no longer points to us
        }

        // can safely delete a now

        delete this->a;
    }
};
于 2012-11-29T04:12:35.823 回答