C++ C++;循环析构函数

C++ C++;循环析构函数,c++,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.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;i、 呼叫B的解构器。 B的解构器会说删除A。 死亡循环无限

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


谢谢

此代码具有未定义的行为。第一次执行
删除a
,然后调用
删除b
。然后
B::~B
调用
删除a
,这将导致双重空闲

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

对于这个特定的应用程序,您可能需要考虑<代码> > <代码>“拥有”<代码> b>代码>反之亦然。这样,例如,
A
总是负责调用
delete B
。这在您有对象树的情况下很常见,其中子对象可以引用父对象,反之亦然。在这种情况下,家长“拥有”孩子,并负责删除孩子

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

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

使用智能指针(即)和弱指针(即)而不是纯指针


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

这是循环数据依赖的问题,而不是循环析构函数依赖的问题。如果指针链
a->b->a->b->…->a..
最终导致
NULL
,析构函数将终止;如果指针的轨迹指向起始点,那么在删除同一对象两次时将出现未定义的行为


问题与删除循环链表没有太大区别:如果不小心,您可能会返回完整的循环。您可以使用一种常见的技术来检测结构中的
a
/
B
循环,并在触发析构函数链之前将“返回指针”设置为
NULL

这里有一个简单的解决方案:在删除对象之前,通过将字段设置为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;
-


首先,你的设计很糟糕。如果
A
B
相互关联,取出
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 ;
    }
  }
} ;
注意:这仅适用于
A
B
相互指向的场景

它不适用于这种情况


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

在调用
delete B
之前,将
This->B->A
设置为NULL。正如您所发现的,这样编写代码是个坏主意;-)如果你想开枪打自己的脚,那就开枪打自己的脚。“有没有更好的方法来写代码来解决这个问题?”不要写这个代码,问题解决了。我不是在开玩笑。如果你提出了一个真正的问题,我们可以告诉你如何解决它。我所说的“real”是指具有实际有意义名称的类,它们实际上做了一些可能有用的事情,或者至少尝试做一些有用的事情。然后,我们可以向您展示如何重构它,使其达到相同的目的(即,仍然可以做您正在尝试做的有用的事情),但不会出现问题。一旦您停止用锤子敲击拇指,您的拇指会感觉更好。首先,不要这样编码;你怎么知道的?你不是建造它的人!这就是“如果”的含义。构造它时绝对没有问题,因此它确实会导致
NULL
。对不起,我没有看到该句开头的“if…”。删除了向下投票。我觉得这是为了避免问题,而不是为了解决潜在的对象设计问题。
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 ;
    }
  }
} ;