:
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è。
有没有更好的方法来编写代码来解决这个问题? 这不是一个迫切的问题,只是好奇。
谢谢!
答案 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_ptr
和weak_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)
首先,你的设计很糟糕。如果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
从公共基类继承,否则这很难做到。