簡體   English   中英

C ++循環析構函數

[英]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_ptrweak_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;
    }
};

首先,這里的設計不好。 如果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指向彼此的情況

在此處輸入圖片說明

並不適用於這種情況

在此處輸入圖片說明

以上情況是圓鏈表,你不得不期待this->b->a->b ,直到你到達this再次,設置最后指針為NULL,則啟動銷毀過程。 除非AB繼承自一個通用基類,否則這將很難做到。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM