デストラクタを手動で呼び出すことは、常に悪い設計の兆候ですか?


83

私は考えていました:彼らはあなたが手動でデストラクタを呼んでいるなら-あなたは何か間違ったことをしていると言います。しかし、それは常に当てはまりますか?反例はありますか?手動で呼び出す必要がある状況、またはそれを回避することが難しい/不可能/非現実的な状況ですか?


dtorを呼び出した後、オブジェクトを再度呼び出さずに、オブジェクトの割り当てを解除するにはどうすればよいですか?
ssube 2013年

2
@peachykeen:プレースメントnewを呼び出して、古いオブジェクトの代わりに新しいオブジェクトを初期化します。一般的には良い考えではありませんが、前代未聞ではありません。
D.Shawley 2013年

14
疑わしい仕様から直接来ていない「常に」と「決して」という言葉を含む「ルール」を見てください。それらを教えているほとんどの場合、あなたが知っておくべきことを隠したいのですが、彼はそうしません教える方法を知っています。大人が子供にセックスについての質問に答えるのと同じように。
Emilio Garavaglia 2013年

配置テクニックstroustrup.com/bs_faq2.html#placement-deleteを使用してオブジェクトを構築する場合は問題ないと思います(ただし、これはかなり低レベルのものであり、そのようなレベルでもソフトウェアを最適化する場合にのみ使用されます)
bruziuz

回答:


94

operator new()std::nothrow」オーバーロードを使用する場合を除いて、オブジェクトがオーバーロードされた形式のを使用して構築された場合は、デストラクタを手動で呼び出す必要があります。

T* t0 = new(std::nothrow) T();
delete t0; // OK: std::nothrow overload

void* buffer = malloc(sizeof(T));
T* t1 = new(buffer) T();
t1->~T(); // required: delete t1 would be wrong
free(buffer);

ただし、上記のようにデストラクタを明示的に呼び出すような低レベルでメモリを管理すること、設計が悪いことを示しています。おそらく、それは実際には悪い設計であるだけでなく、完全に間違っています(はい、明示的なデストラクタの後に代入演算子でコピーコンストラクタ呼び出しを使用すること悪い設計であり、間違っている可能性があります)。

C ++ 2011では、明示的なデストラクタ呼び出しを使用する別の理由があります。一般化された共用体を使用する場合、現在のオブジェクトを明示的に破棄し、表現されるオブジェクトのタイプを変更するときに、placementnewを使用して新しいオブジェクトを作成する必要があります。また、ユニオンが破棄されたときに、破棄が必要な場合は、現在のオブジェクトのデストラクタを明示的に呼び出す必要があります。


26
「オーバーロードされた形式を使用する」と言う代わりにoperator new、正しいフレーズは「使用するplacement new」です。
Remy Lebeau 2013年

5
@RemyLebeau:ええと、私はoperator new(std::size_t, void*)(そして配列のバリエーション)だけでなく、のすべてのオーバーロードされたバージョンについて話していることを明確にしたいと思いましたoperator new()
ディートマークール2013年

操作の計算中にオブジェクトを変更せずにオブジェクトをコピーして操作を実行する場合はどうでしょうか。temp = Class(object); temp.operation(); object.~Class(); object = Class(temp); temp.~Class();
Jean-Luc Nacif Coelho 2017

yes, using an explicit destructor followed by a copy constructor call in the assignment operator is a bad design and likely to be wrong。なんでそんなこというの?デストラクタが取るに足らない、または取るに足らないものに近い場合、オーバーヘッドが最小限に抑えられ、DRY原理の使用が増えると思います。このような場合に移動を使用する場合は、operator=()スワップを使用するよりも優れている可能性があります。YMMV。
エイドリアン

1
@Adrian:デストラクタを呼び出してオブジェクトを再作成すると、オブジェクトのタイプが非常に簡単に変更されます。割り当ての静的タイプでオブジェクトが再作成されますが、動的タイプは異なる場合があります。これは、クラスにvirtual関数がある場合(virtual関数は再作成されない)、そうでない場合はオブジェクトが部分的に[再]構築される場合に実際に問題になります。
ディートマークール2018

101

すべての回答は特定のケースを説明していますが、一般的な回答があります。

オブジェクトが存在するメモリを解放せずに、(C ++の意味で)オブジェクトを破棄する必要があるたびに、dtorを明示的に呼び出します。

これは通常、メモリの割り当て/割り当て解除がオブジェクトの構築/破棄とは独立して管理されているすべての状況で発生します。そのような場合、構築は既存のメモリチャンクに新しい配置を介して行われ、破棄は明示的なdtor呼び出しを介して行われます。

これが生の例です:

{
  char buffer[sizeof(MyClass)];

  {
     MyClass* p = new(buffer)MyClass;
     p->dosomething();
     p->~MyClass();
  }
  {
     MyClass* p = new(buffer)MyClass;
     p->dosomething();
     p->~MyClass();
  }

}

別の顕著な例では、デフォルトであるstd::allocatorが使用するstd::vector要素が中に構築される:vectorpush_back、それは要素contructionを事前に存在するように、メモリは、チャンクに割り当てられています。したがって、vector::erase要素を破棄する必要がありますが、必ずしもメモリの割り当てを解除する必要はありません(特に、新しいpush_backがすぐに発生する必要がある場合...)。

厳密なOOPの意味での「悪い設計」(メモリではなくオブジェクトを管理する必要があります。オブジェクトがメモリを必要とするという事実は「インシデント」です)、「低レベルプログラミング」の「良い設計」、またはメモリがデフォルトでoperator new購入する「無料ストア」から取得されません。

コードの周囲でランダムに発生する場合は悪い設計ですが、その目的のために特別に設計されたクラスに対してローカルで発生する場合は良い設計です。


8
なぜこれが受け入れられた答えではないのかについて興味があります。
Francis Cugler 2018年

11

いいえ、2回呼び出されるため、明示的に呼び出すべきではありません。1回は手動呼び出し用で、もう1回はオブジェクトが宣言されているスコープが終了するときです。

例えば。

{
  Class c;
  c.~Class();
}

本当に同じ操作を実行する必要がある場合は、別のメソッドが必要です。

プレースメントを使用して動的に割り当てられたオブジェクトのデストラクタを呼び出したいが、必要なものが聞こえないという特定の状況がありますnew


11

いいえ、状況によっては、合法で優れたデザインの場合もあります。

デストラクタを明示的に呼び出す必要がある理由と時期を理解するために、「new」と「delete」で何が起こっているかを見てみましょう。

T* t = new T;内部でオブジェクトを動的に作成するには、 次の手順に従います。1。sizeof(T)メモリが割り当てられます。2. Tのコンストラクターが呼び出され、割り当てられたメモリが初期化されます。new演算子は、割り当てと初期化の2つのことを行います。

delete t;ボンネットの下のオブジェクトを破壊するには:1。Tのデストラクタが呼び出されます。2.そのオブジェクトに割り当てられたメモリが解放されます。演算子deleteは、破棄と割り当て解除の2つのことも行います。

初期化を行うコンストラクタと破棄を行うデストラクタを記述します。デストラクタを明示的に呼び出すと、破棄のみが実行され、割り当て解除は実行されません

したがって、デストラクタを明示的に呼び出す正当な使用法は、「オブジェクトを破棄したいだけですが、メモリ割り当てを解放しません(または解放できません)」ということです。

この一般的な例は、動的に割り当てる必要がある特定のオブジェクトのプールにメモリを事前に割り当てることです。

新しいオブジェクトを作成するときは、事前に割り当てられたプールからメモリのチャンクを取得し、「新規配置」を実行します。オブジェクトの処理が完了したら、デストラクタを明示的に呼び出して、クリーンアップ作業があれば終了することをお勧めします。ただし、演​​算子deleteが行ったように、実際にはメモリの割り当てを解除することはありません。代わりに、チャンクをプールに戻して再利用します。



6

割り当てを初期化から分離する必要があるときはいつでも、デストラクタを手動で新しく明示的に呼び出す必要があります。現在、標準のコンテナーがあるため、これが必要になることはめったにありませんが、新しい種類のコンテナーを実装する必要がある場合は、それが必要になります。


3

それらが必要な場合があります:

私が取り組んでいるコードでは、アロケーターで明示的なデストラクタ呼び出しを使用しています。新しい配置を使用してメモリブロックをstlコンテナに返す単純なアロケーターを実装しています。破壊することで私は持っています:

  void destroy (pointer p) {
    // destroy objects by calling their destructor
    p->~T();
  }

構築中:

  void construct (pointer p, const T& value) {
    // initialize memory with placement new
    #undef new
    ::new((PVOID)p) T(value);
  }

プラットフォーム固有のallocおよびdeallocメカニズムを使用して、allocate()で割り当てが行われ、deallocate()でメモリの割り当てが解除されます。このアロケータは、ダグリアマロックをバイパスし、たとえばWindowsでLocalAllocを直接使用するために使用されました。


1

私はこれを行う必要がある3つの機会を見つけました:

  • memory-mapped-ioまたは共有メモリによって作成されたメモリ内のオブジェクトの割り当て/割り当て解除
  • C ++を使用して特定のCインターフェイスを実装する場合(はい、残念ながら今日でもこれは発生します(変更するのに十分な影響力がないため))
  • アロケータクラスを実装する場合

1

手動でデストラクタを呼び出す必要がある状況に遭遇したことはありません。Stroustrupでさえ、それは悪い習慣だと主張していることを覚えているようです。


1
あなたは正しいです。しかし、私は新しいプレースメントを使用しました。デストラクタ以外のメソッドにクリーンアップ機能を追加することができました。デストラクタはそこにあるので、削除するときに「自動的に」呼び出すことができます。手動で破棄したいが割り当てを解除したくない場合は、単に「onDestruct」と書くことができますね。私はあなただけ解放し破壊していないしたいと思う時々削除する必要がありますので、オブジェクトのデストラクタでその破壊を行う必要があります例や、他の時間がある場合は...聞いて興味がある
Lieuwe

その場合でも、デストラクタ内からonDestruct()を呼び出すことができます。そのため、手動でデストラクタを呼び出すケースはまだありません。
Lieuwe 2013年

4
@JimBalter:の生みの親C+
マーク・K・コーワン

@MarkKCowan:C +とは何ですか?C ++である必要があります
デストラクタ

1

これはどうですか?
コンストラクタから例外がスローされた場合、デストラクタは呼び出されないため、例外の前にコンストラクタで作成されたハンドルを破棄するには、デストラクタを手動で呼び出す必要があります。

class MyClass {
  HANDLE h1,h2;
  public:
  MyClass() {
    // handles have to be created first
    h1=SomeAPIToCreateA();
    h2=SomeAPIToCreateB();        
    try {
      ...
      if(error) {
        throw MyException();
      }
    }
    catch(...) {
      this->~MyClass();
      throw;
    }
  }
  ~MyClass() {
    SomeAPIToDestroyA(h1);
    SomeAPIToDestroyB(h2);
  }
};

1
これは疑わしいようです。コンストラクターがスローするとき、オブジェクトのどの部分が構築され、どの部分が構築されていないかがわかりません(またはわからない場合があります)。したがって、たとえば、どのサブオブジェクトに対してデストラクタを呼び出すかはわかりません。または、コンストラクターによって割り当て解除するリソースのどれか。
バイオレットキリン2018年

@VioletGiraffeサブオブジェクトがスタック上に構築されている場合、つまり「新規」ではない場合、それらは自動的に破棄されます。それ以外の場合は、デストラクタで破棄する前に、それらがNULLかどうかを確認できます。リソースと同じ
CITBL 2018

ctorここに書いた方法は間違っています。まさにあなたが自分で提供した理由です。リソースの割り当てが失敗した場合、クリーンアップに問題があります。'ctor'はを呼び出さないでくださいthis->~dtor()構築されたオブジェクトでdtor呼び出す必要があります。この場合、オブジェクトはまだ構築されていません。何が起こっても、はクリーンアップを処理する必要があります。コード内では、何かがスローされた場合に自動クリーンアップを処理するようなutilsを使用する必要があります。自動クリーンアップをサポートするようにクラスのフィールドを変更することも良い考えです。ctorctorstd::unique_ptrHANDLE h1, h2
ケツァルコアトル

:CTORがどのように見えるべきであるとこれは意味、MyClass(){ cleanupGuard1<HANDLE> tmp_h1(&SomeAPIToDestroyA) = SomeAPIToCreateA(); cleanupGuard2<HANDLE> tmp_h2(&SomeAPIToDestroyB) = SomeAPIToCreateB(); if(error) { throw MyException(); } this->h1 = tmp_h1.release(); this->h2 = tmp_h2.release(); }そしてそれはそれです。危険な手動クリーンアップはなく、すべてが安全になるまで部分的に構築されたオブジェクトにハンドルを保存する必要はありません。HANDLE h1,h2クラスをcleanupGuard<HANDLE> h1;etcに変更すると、まったく必要ない場合もありdtorます。
ケツァルコアトル

の実装cleanupGuard1cleanupGuard2は、関連するxxxToCreateリターンを実行するものと、関連するパラメータを実行するものに依存しますxxxxToDestroy。それらが単純な場合は、何も書く必要がないかもしれませんstd::unique_ptr<x,deleter()>。どちらの場合も(または同様の)トリックを実行できることがよくあるからです。
ケツァルコアトル

-2

デストラクタを手動で呼び出す必要がある別の例を見つけました。いくつかのタイプのデータの1つを保持するバリアントのようなクラスを実装したとします。

struct Variant {
    union {
        std::string str;
        int num;
        bool b;
    };
    enum Type { Str, Int, Bool } type;
};

場合はVariant、インスタンスを持っていたstd::string、そして今あなたが組合に異なるタイプを割り当てている、あなたは破壊しなければならないstd::string最初に。コンパイラはそれを自動的には行いません


-4

デストラクタを呼び出すのが完全に合理的であると思う別の状況があります。

オブジェクトを初期状態に復元するための「リセット」タイプのメソッドを作成する場合、デストラクタを呼び出して、リセットされている古いデータを削除することは完全に合理的です。

class Widget
{
private: 
    char* pDataText { NULL  }; 
    int   idNumber  { 0     };

public:
    void Setup() { pDataText = new char[100]; }
    ~Widget()    { delete pDataText;          }

    void Reset()
    {
        Widget blankWidget;
        this->~Widget();     // Manually delete the current object using the dtor
        *this = blankObject; // Copy a blank object to the this-object.
    }
};

1
cleanup()この場合デストラクタで呼び出される特別なメソッドを宣言した場合、見た目はすっきりしませんか?
バイオレットキリン2016年

2つの場合にのみ呼び出される「特別な」メソッド?確かに...それは完全に正しいように聞こえます(/皮肉)。メソッドは一般化され、どこからでも呼び出せるようにする必要があります。オブジェクトを削除したい場合、そのデストラクタを呼び出すことに何の問題もありません。
abelenky 2016年

4
この状況では、デストラクタを明示的に呼び出さないでください。とにかく、代入演算子を実装する必要があります。
レミ2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.