2

ファイルああ:

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。deathloopléinfinitèを削除すると言います。

この問題を解決するためのコードを書くためのより良い方法はありますか?これは差し迫った質問ではなく、ただ好奇心が強いです。

ありがとう!

4

5 に答える 5

5

純粋なポインターの代わりに、スマート ポインター (つまりstd::shared_ptr) と弱いポインター (つまり) を使用します。std::weak_ptr

どのクラスが実際にどのクラスを所有しているかを定義する必要があります。どちらも他方を所有していない場合は、両方ともウィーク ポインターにする必要があります。

于 2012-11-29T02:46:28.557 に答える
3

このコードの動作は未定義です。最初delete aに発生し、次に をA::~A呼び出しますdelete b。次に、 をB::~B呼び出しますdelete a。これにより、ダブル フリーが発生します。

あなたの実装は、無限ループを含む、または単に「動作しているように見える」など、そのような場合に必要なことを何でも行うことができます。オブジェクトがdeleteed されるのは 1 回だけであることを確認する必要があります。

この特定のアプリケーションでは、A「所有」Bまたはその逆を検討することをお勧めします。そうすれば、 egAは常に を呼び出す責任がありdelete Bます。これは、子が親を参照できるオブジェクトのツリーのようなものがある場合によく見られ、その逆も同様です。このような場合、親は子を「所有」し、子を削除する責任を負います。

shared_ptrそれができない場合は、 andを使用して何らかの解決策を考え出すことができるかもしれませんweak_ptr(これは循環的であるため、shared_ptr単独では役に立ちません)。ただし、このような事態が発生しないように設計することを強く検討する必要があります。

この特定の例では、クラスは へのポインタを所有しAていませんB--へmainのポインタを所有していますB。したがって、 のデストラクタ コードはAおそらく何も削除すべきではありません -- によって処理される必要がありますmain(または、できればstd::unique_ptr内のインスタンスmain)。

于 2012-11-29T02:45:42.643 に答える
2

これは、デストラクタの循環依存ではなく、データの循環依存の問題です。ポインターのチェーンがa->b->a->b->...->a...最終的に につながる場合NULL、デストラクタは終了します。ポインターの軌跡が先頭に戻る場合、同じオブジェクトを 2 回削除すると、未定義の動作が発生します。

問題は、循環リンク リストを削除する場合と大差ありません。注意しないと、元に戻ってしまう可能性があります。カメとウサギとして一般に知られている一般的な手法を使用して、構造内のA/ループを検出し、デストラクタのチェーンをトリガーBする前に「バック ポインタ」を に設定できます。NULL

于 2012-11-29T02:46:41.663 に答える
1

まず第一に、あなたはここで悪いデザインをしています。とが相互にリンクされていて、Aを削除すると it がリンクされ、その逆の場合、その関係を親子のような関係にリファクタリングする方法が必要です。 可能であれば「親」であるべきです。そのため、 a を破棄してもリンク先は破棄されませんが、 anを破棄しても含まれる は破棄されます。BABABBAAB

もちろん、常にそのようにうまくいくとは限りません。時には、この避けられない循環依存が発生することもあります。それを機能させるには (リファクタリングする機会があるまで!)破壊のサイクルを断ち切ります。

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

于 2013-04-30T16:13:04.973 に答える
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;
    }
};
于 2012-11-29T04:12:35.823 に答える