C++ C++;循环析构函数
在文件a.h中: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->
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 ;
}
}
} ;