私は考えていました:彼らはあなたが手動でデストラクタを呼んでいるなら-あなたは何か間違ったことをしていると言います。しかし、それは常に当てはまりますか?反例はありますか?手動で呼び出す必要がある状況、またはそれを回避するのが難しい/不可能/非現実的な状況ですか?
12 に答える
すべての回答は特定のケースを説明していますが、一般的な回答があります。
オブジェクトが存在するメモリを解放せずに、(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();
}
}
もう1つの注目すべき例は、std::allocator
によって使用される場合のデフォルトですstd::vector
。要素は中に構築さvector
れpush_back
ますが、メモリはチャンクで割り当てられるため、要素構造がすでに存在します。したがって、vector::erase
要素を破棄する必要がありますが、必ずしもメモリの割り当てを解除する必要はありません(特に、新しいpush_backがすぐに発生する必要がある場合...)。
厳密なOOPの意味での「悪い設計」(メモリではなくオブジェクトを管理する必要があります。オブジェクトがメモリを必要とするという事実は「インシデント」です)、「低レベルプログラミング」の「良い設計」、またはメモリがデフォルトでoperator new
購入する「無料ストア」から取得されません。
コードの周囲でランダムに発生する場合は悪い設計ですが、その目的のために特別に設計されたクラスに対してローカルで発生する場合は良い設計です。
オブジェクトがオーバーロードされた形式のを使用して構築された場合は、 " "オーバーロード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では、明示的なデストラクタ呼び出しを使用する別の理由があります。一般化された和集合を使用する場合、現在のオブジェクトを明示的に破棄し、表現されたオブジェクトのタイプを変更するときに新しい配置を使用して新しいオブジェクトを作成する必要があります。また、ユニオンが破棄された場合、破棄が必要な場合は、現在のオブジェクトのデストラクタを明示的に呼び出す必要があります。
いいえ、2回呼び出されるため、明示的に呼び出すべきではありません。1回は手動呼び出し用で、もう1回はオブジェクトが宣言されているスコープが終了するときです。
例えば。
{
Class c;
c.~Class();
}
本当に同じ操作を実行する必要がある場合は、別のメソッドが必要です。
プレースメントを使用して動的に割り当てられたオブジェクトのデストラクタを呼び出したい場合がありますが、必要なnew
ものが聞こえない場合があります。
いいえ、状況によっては、合法で優れたデザインの場合もあります。
デストラクタを明示的に呼び出す必要がある理由と時期を理解するために、「new」と「delete」で何が起こっているかを見てみましょう。
内部でオブジェクトを動的に作成するには、 T* t = new T;
次の手順に従います。1. sizeof(T)メモリが割り当てられます。2. Tのコンストラクターが呼び出され、割り当てられたメモリーが初期化されます。new演算子は、割り当てと初期化の2つのことを行います。
内部のオブジェクトを破棄するにはdelete t;
、次の手順に従います。1.Tのデストラクタが呼び出されます。2.そのオブジェクトに割り当てられたメモリが解放されます。演算子deleteは、破棄と割り当て解除の2つのことも行います。
初期化を行うコンストラクタと破棄を行うデストラクタを記述します。デストラクタを明示的に呼び出すと、破棄のみが実行され、割り当て解除は実行されません。
したがって、デストラクタを明示的に呼び出す正当な使用法は、「オブジェクトを破棄したいだけですが、メモリ割り当てを解放しません(または解放できません)」ということです。
この一般的な例は、動的に割り当てる必要がある特定のオブジェクトのプールにメモリを事前に割り当てることです。
新しいオブジェクトを作成するときは、事前に割り当てられたプールからメモリのチャンクを取得し、「新規配置」を実行します。オブジェクトの処理が完了したら、デストラクタを明示的に呼び出して、クリーンアップ作業があればそれを終了することをお勧めします。ただし、演算子deleteが行ったように、実際にはメモリの割り当てを解除することはありません。代わりに、チャンクをプールに戻して再利用します。
FAQで引用されているように、配置newを使用する場合は、デストラクタを明示的に呼び出す必要があります。
これは、明示的にデストラクタを呼び出す唯一の時間です。
私はこれがめったに必要とされないことに同意します。
割り当てを初期化から分離する必要があるときはいつでも、手動でデストラクタを新しく明示的に呼び出す必要があります。現在、標準のコンテナーがあるため、これが必要になることはめったにありませんが、新しい種類のコンテナーを実装する必要がある場合は、それが必要になります。
それらが必要な場合があります:
私が取り組んでいるコードでは、アロケータで明示的なデストラクタ呼び出しを使用しています。新しい配置を使用してメモリブロックを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を直接使用するために使用されました。
私はこれを行う必要がある3つの機会を見つけました:
- memory-mapped-ioまたは共有メモリによって作成されたメモリ内のオブジェクトの割り当て/割り当て解除
- C ++を使用して特定のCインターフェイスを実装する場合(はい、残念ながら今日でもこれは発生します(変更するのに十分な影響力がないため))
- アロケータクラスを実装する場合
これはどうですか?
コンストラクタから例外がスローされた場合、デストラクタは呼び出されないため、例外の前にコンストラクタで作成されたハンドルを破棄するには、デストラクタを手動で呼び出す必要があります。
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);
}
};
デストラクタを手動で呼び出す必要がある状況に遭遇したことはありません。Stroustrupでさえ、それは悪い習慣だと主張していることを覚えているようです。
デストラクタを手動で呼び出す必要がある別の例を見つけました。いくつかのタイプのデータの1つを保持するバリアントのようなクラスを実装したとします。
struct Variant {
union {
std::string str;
int num;
bool b;
};
enum Type { Str, Int, Bool } type;
};
Variant
インスタンスがを保持していstd::string
て、ユニオンに別のタイプを割り当てている場合は、最初のタイプを破棄する必要がありますstd::string
。コンパイラはそれを自動的に行いません。
デストラクタを呼び出すのが完全に合理的であると思う別の状況があります。
オブジェクトを初期状態に復元するための「リセット」タイプのメソッドを作成する場合、リセットされている古いデータを削除するためにデストラクタを呼び出すことは完全に合理的です。
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.
}
};