3

デストラクタに関するフォーラムの投稿を大量に読んで、完全に混乱してしまいます。

への呼び出しごとに 1 回deleteデストラクタ ( を使用)を呼び出すと言う人もいます。デストラクタは、さまざまな状況で自動的に呼び出されると言う人もいます。つまり 、ポインタが再割り当てされたとき、オブジェクトがスコープ外になったときなどです。オブジェクトが以前の自己のコピーとして存在する戻り値である間に、ポインターがスコープ外になることを示唆する人もいます (これは、最初に ? で作成されたため、明示的な破棄が必要ですか?newnew

同じデストラクタを複数回呼び出すとメモリが破損するため、破損を避けるためにすべての呼び出しを連携させる必要があるという提案があるようです。そうでない場合は、より高度なオブジェクト管理システムを実装するか、所有権を厳格に管理する必要があります。delete*pointer = NULL;

デストラクタの呼び出しシーケンスに関する議論の意味を理解できないようです。つまり、呼び出しは1)基本スーパークラスで開始され、特定のクラスにカスケードされ、途中ですべての仮想化されたデストラクタが呼び出されます.2)インスタンス化されたデストラクタで開始されます.クラスからスーパークラスに移動するか、または 3) クラスがスコープ外になったときにそのクラスが持つ特定のキャストから開始し、インスタンス化されたクラスと基本クラスの両方に向かってトラバースします。デストラクタをカスケードする

最終的には、オブジェクトを削除する方法や時期、オブジェクトが参照するすべてのオブジェクトを削除する責任があるかどうか、オブジェクトが複数回参照される適切なオブジェクト指向の削除ルーチンをきれいに処理する方法を厳密に知りません。頭の中がぐちゃぐちゃ。お分かりのように、私は単一の明確な質問をすることはできません。誰かが、単一の「正しい」アプローチではないにしても、少なくともオブジェクト削除に対する業界のベストプラクティスについて、クリーンで簡潔な議論を提供できることを本当に望んでいます.

4

3 に答える 3

8

デストラクタが異なる方法で呼び出される 3 種類の割り当てがあります。

自動割り当て

これらのオブジェクトは、自動メモリ (簡単に言うと、スタック) に存在します。

int main()
{
  A a;
  //...
}

のデストラクタは、スコープ外aになると自動的に呼び出されます (終了)。a}

ダイナミック アロケーション

オブジェクトは動的メモリ (ヒープ) に常駐します。それらはで割り当てられnew、デストラクタが呼び出されるためには、次のように呼び出す必要がありますdelete

int main()
{
  A* a = new A;
  delete a;    //destructor called
}

この場合NULL、. これに関しては 2 つの考え方があります (個人的にはお勧めしません)。動機は、に設定しないと、もう一度呼び出してプログラムをクラッシュさせる可能性があるということです。どちらが正しい。しかし、もう一度呼び出した場合、それは既にバグまたはロジックの何かが間違っているため、コードが正しく実行されているように見せかけてマスクするべきではありません。adeletedeleteaNULLdelete

静的割り当て

オブジェクトはstaticメモリに常駐します。それらがどこに割り当てられているかに関係なく、デストラクタはプログラムの終了時に自動的に呼び出されます。

A a; //namespace scope

int main()
{
}

ここで、As デストラクタは、プログラムの終了後、終了時に呼び出されmainます。

于 2013-08-05T09:28:22.593 に答える
3

C++ 言語ではメモリ管理がプログラマの手に委ねられているため、このレベルの混乱が見られることがあります。

Luchian Grigore が言ったことを繰り返しますが、記憶には主に 3 つのタイプがあります。

  • 自動保管(スタック)
  • 動的ストレージ (ヒープ)
  • 静的ストレージ

オブジェクトを自動ストレージに割り当てた場合、オブジェクトはスコープが終了すると破棄されます。例えば

 void foo() {
     MyClass myclass_instance;
     myclass_instance.doSomething();
 }

上記の場合、関数が終了するmyclass_instanceと自動的に破棄されます。

代わりに でヒープにオブジェクトを割り当てる場合はnew、 でデストラクタを呼び出すのはあなたの責任deleteです。

C++ でも、オブジェクトはサブオブジェクトを持つことができます。例えば:

class MyBiggerClass {
    MyClass x1;
    MyClass x2;
    ...
};

これらのサブオブジェクトは、包含オブジェクトが割り当てられているのと同じメモリに割り当てられます

void foo() {
    MyBiggerClass big_instance;
    MyBiggerClass *p = new MyBiggerClass();
    ...
    delete p;
}

上記の場合、2 つのサブオブジェクトbig_instance.x1big_instance.x2は自動ストレージ (スタック) に割り当てられ、p->x1p->x2はヒープに割り当てられます。

ただし、この場合、呼び出す必要はないことに注意してくださいdelete p->x1;(コンパイルエラー、p->x1ポインターではありません) またはdelete &(p->x1);(構文的に有効ですが、ヒープに明示的に割り当てられたのではなく、別のサブオブジェクトとして割り当てられたため、論理的な間違いです)物体)。メインオブジェクトpを削除するだけです。

別の複雑さは、オブジェクトが他のオブジェクトへのポインターを直接含めるのではなく保持する可能性があることです。

class MyOtherBigClass {
    MyClass *px1;
    MyClass *px2;
};

この場合MyOtherBigClass、サブオブジェクトのメモリを見つける必要があるのはコンストラクタであり、サブオブジェクト~MyOtherBigClassを破棄してメモリを解放する必要があるのはコンストラクタです。

C++ では、生のポインターを破棄してもコンテンツは自動的に破棄されません。

単純なケースの基本クラスは、隠し埋め込みサブオブジェクトと見なすことができます。つまり、ベース オブジェクトのインスタンスが派生オブジェクトに埋め込まれているようなものです。

class MyBaseClass {
    ...
};

class MyDerivedClass : MyBaseClass {
    MyBaseClass __base__;  // <== just for explanation of how it works: the base
                           //     sub-object is already present, you don't
                           //     need to declare it and it's a sub-object that
                           //     has no name. In the C++ standard you can find
                           //     this hidden sub-object referenced quite often.
    ...
};

これは、言語によって自動的に処理されるため、派生オブジェクトのデストラクタがベース オブジェクトのデストラクタを呼び出す必要がないことを意味します。仮想ベースの場合はより複雑ですが、ベース デストラクタの呼び出しは自動的に行われます。

メモリ管理がプログラマーの管理下にあることを考えると、常にオブジェクト リークや複数の破壊につながる複雑なコードの混乱をプログラマーが回避するのに役立ついくつかの戦略が登場しています。

  1. インスタンスの有効期間をどのように処理するかを慎重に計画してください。後で修正することは不可能になるため、これを後付けとして残すことはできません。すべてのオブジェクト インスタンスについて、作成者と破棄者を明確にする必要があります。

  2. オブジェクトをいつ破棄するかを事前に計画することが不可能な場合は、参照カウンターを使用します。すべてのオブジェクトについて、それを参照しているポインターの数を追跡し、この数がゼロに達したらオブジェクトを破棄します。これを処理できるスマート ポインターがあります。

  3. すでに破棄されているオブジェクトへのポインターを保持しないでください。

  4. 含まれているオブジェクトの有効期間を処理するために明示的に設計されたクラスであるコンテナーを使用します。例はstd::vectorまたはstd::mapです。

于 2013-08-05T10:01:46.710 に答える