[英]C++ cyclic destructors
在文件啊:
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 *對象的指針,並且我們想要刪除它:
// ...
A *a = new A();
B *b = new B();
A->b = b;
B->a = a;
// ...
delete a;
// ...
A的解構函數會說刪除B; 即調用B的解構函數。 B的解構函數會說刪除A.死循環léinfinitè。
有沒有更好的方法編寫代碼來解決此問題? 這不是緊迫的問題,只是好奇。
謝謝!
使用智能指針(即std::shared_ptr
)和弱指針(即std::weak_ptr
)來代替純指針。
您必須定義哪個類真正擁有哪個類。 如果沒有一個擁有另一個,則兩個都應該是弱指針。
此代碼具有未定義的行為。 首先delete a
,然后A::~A
調用delete b
。 然后B::~B
調用delete a
,從而導致雙倍空閑。
在這種情況下,您的實現被允許做任何想做的事情,包括無限循環,或者只是“看起來可以工作”。 您必須確保對象僅被delete
一次。
對於此特定應用程序,您可能要考慮讓A
“擁有” B
,反之亦然。 這樣,例如A
始終負責調用delete B
這在您有某種物體樹的情況下很常見,孩子可以在其中引用父母,反之亦然。 在這種情況下,父母“擁有”孩子並負責刪除孩子。
如果您不能做到這一點,則可以使用shared_ptr
和weak_ptr
提出一些解決方案(這是循環的,因此僅shared_ptr
不能幫上忙); 但是您應該強烈考慮進行設計,以免發生這種情況。
在這個具體的例子,類A
不擁有指針B
- main
擁有指針到B
。 因此, A
的析構函數代碼可能不應刪除任何內容,而應由main
處理(或者希望在main
內部處理std::unique_ptr
實例)。
這是循環數據依賴性而不是循環析構函數依賴性的問題。 如果指針鏈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;
--
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;
}
};
首先,這里的設計不好。 如果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
繼承自一個通用基類,否則這將很難做到。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.