C ++循环析构函数

时间:2012-11-29 02:37:42

标签: c++

文件a.h中的

class B;
class A
{
public:
    B *b;
    A(B *b = nullptr)
    {
        this->b = b;
    }
    ~A()
    {
        delete this->b;
    }
};
文件b.h中的

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

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

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

delete a;

// ...

A的解构函数会说删除B;即致电B的解构主义者。 B的解构函数会说删除A. 死循环léinfinitè。

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

谢谢!

5 个答案:

答案 0 :(得分:4)

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

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

答案 1 :(得分:3)

此代码具有未定义的行为。第一个delete a发生,然后A::~A调用delete b。然后B::~B调用delete a,这会导致双倍免费。

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

对于此特定应用,您可能需要考虑让A"拥有" B,反之亦然。这样,例如A始终负责致电delete B。这种情况很常见,在这种情况下,您拥有一些物体树,其中儿童可以引用父母,反之亦然。在这种情况下,父母"拥有"孩子们负责删除孩子。

如果你不能这样做,你可以用shared_ptrweak_ptr提出一些解决方案(这是循环的,所以shared_ptr单独没有&#39} ; t帮助你);但你应该强烈考虑设计,以免出现这种情况。

在此特定示例中,类A不拥有指向B的指针 - main拥有指向B的指针。因此,A的析构函数代码可能不应删除任何内容 - 应该由main(或者更确切地说,std::unique_ptr} main}内的{{1}}个实例处理。

答案 2 :(得分:2)

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

问题与删除循环链表没有太大区别:如果不小心,可以回到正圆。您可以使用通常称为tortoise and hare的常用技术来检测结构中的A / B循环,并在触发链接之前将“后向指针”设置为NULL析构函数。

答案 3 :(得分: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;
    }
};

答案 4 :(得分:1)

首先,你的设计很糟糕。如果AB相互关联,并且取出A会破坏它链接的B,反之亦然,那么必须有一种方法可以将这种关系重构为父母 - 孩子的关系。 A应该是"父母"如果可能的话B,那么销毁B并不会销毁它所链接的A,但是销毁A会破坏它所包含的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相互指向的情况

enter image description here

不适用于这种情况

enter image description here

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