web-dev-qa-db-ja.com

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

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

73
Violet Giraffe

オブジェクトが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);

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

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

85
Dietmar Kühl

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

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

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

生の例を次に示します。

{
  char buffer[sizeof(MyClass)];

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

}

もう1つの注目すべき例は、std::allocatorで使用されるデフォルトのstd::vectorです。要素はPush_backの間にvectorで構築されますが、メモリはチャンクで割り当てられるため、要素の構造が事前に存在します。したがって、vector::eraseは要素を破棄する必要がありますが、必ずしもメモリの割り当てを解除するわけではありません(特に、新しいPush_backをすぐに実行する必要がある場合は...)。

厳密なOOPの意味では「悪い設計」です(メモリではなくオブジェクトを管理する必要があります。メモリを必要とするファクトオブジェクトは「インシデント」です)。または、メモリが「フリーストア」から取得されない場合は、デフォルトのoperator newがバイインします。

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

92

いいえ、状況によって異なりますが、合法であり、良い設計である場合もあります。

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

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

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

初期化を行うためのコンストラクターと、破壊を行うためのデストラクターを作成します。デストラクタを明示的に呼び出すと、破棄のみが行われますが、割り当て解除ではなくです。

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

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

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

10
user1252446

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

例えば。

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

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

特定の状況 があります。この場合、newという配置で動的に割り当てられたオブジェクトのデストラクタを呼び出すことができますが、必要なものは聞こえません。

8
Jack

FAQで引用されているように、 配置newを使用する場合は明示的にデストラクタを呼び出す必要があります

これは、デストラクタを明示的に呼び出すことのできる唯一の時間です。

これはめったに必要ではないことに同意します。

8
Luchian Grigore

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

5
James Kanze

必要な場合があります。

私が取り組んでいるコードでは、アロケータで明示的なデストラクタ呼び出しを使用し、新しい配置を使用してメモリブロックを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()でメモリ割り当てが解除されます。このアロケーターは、ダグリーmallocをバイパスし、たとえばWindowsでLocalAllocを直接使用するために使用されました。

3
marcinj

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

1
Lieuwe

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

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
CITBL

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

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