私は考えていました:彼らはあなたが手動でデストラクタを呼んでいるなら-あなたは何か間違ったことをしていると言います。しかし、それは常に当てはまりますか?反例はありますか?手動で呼び出す必要がある状況、またはそれを回避することが難しい/不可能/非現実的な状況ですか?
new
を呼び出して、古いオブジェクトの代わりに新しいオブジェクトを初期化します。一般的には良い考えではありませんが、前代未聞ではありません。
私は考えていました:彼らはあなたが手動でデストラクタを呼んでいるなら-あなたは何か間違ったことをしていると言います。しかし、それは常に当てはまりますか?反例はありますか?手動で呼び出す必要がある状況、またはそれを回避することが難しい/不可能/非現実的な状況ですか?
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では、明示的なデストラクタ呼び出しを使用する別の理由があります。一般化された共用体を使用する場合、現在のオブジェクトを明示的に破棄し、表現されるオブジェクトのタイプを変更するときに、placementnewを使用して新しいオブジェクトを作成する必要があります。また、ユニオンが破棄されたときに、破棄が必要な場合は、現在のオブジェクトのデストラクタを明示的に呼び出す必要があります。
operator new
、正しいフレーズは「使用するplacement new
」です。
operator new(std::size_t, void*)
(そして配列のバリエーション)だけでなく、のすべてのオーバーロードされたバージョンについて話していることを明確にしたいと思いましたoperator new()
。
temp = Class(object); temp.operation(); object.~Class(); object = Class(temp); temp.~Class();
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。
virtual
関数がある場合(virtual
関数は再作成されない)、そうでない場合はオブジェクトが部分的に[再]構築される場合に実際に問題になります。
すべての回答は特定のケースを説明していますが、一般的な回答があります。
オブジェクトが存在するメモリを解放せずに、(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
要素が中に構築される:vector
時push_back
、それは要素contructionを事前に存在するように、メモリは、チャンクに割り当てられています。したがって、vector::erase
要素を破棄する必要がありますが、必ずしもメモリの割り当てを解除する必要はありません(特に、新しいpush_backがすぐに発生する必要がある場合...)。
厳密なOOPの意味での「悪い設計」(メモリではなくオブジェクトを管理する必要があります。オブジェクトがメモリを必要とするという事実は「インシデント」です)、「低レベルプログラミング」の「良い設計」、またはメモリがデフォルトでoperator new
購入する「無料ストア」から取得されません。
コードの周囲でランダムに発生する場合は悪い設計ですが、その目的のために特別に設計されたクラスに対してローカルで発生する場合は良い設計です。
いいえ、状況によっては、合法で優れたデザインの場合もあります。
デストラクタを明示的に呼び出す必要がある理由と時期を理解するために、「new」と「delete」で何が起こっているかを見てみましょう。
T* t = new T;
内部でオブジェクトを動的に作成するには、 次の手順に従います。1。sizeof(T)メモリが割り当てられます。2. Tのコンストラクターが呼び出され、割り当てられたメモリが初期化されます。new演算子は、割り当てと初期化の2つのことを行います。
delete t;
ボンネットの下のオブジェクトを破壊するには:1。Tのデストラクタが呼び出されます。2.そのオブジェクトに割り当てられたメモリが解放されます。演算子deleteは、破棄と割り当て解除の2つのことも行います。
初期化を行うコンストラクタと破棄を行うデストラクタを記述します。デストラクタを明示的に呼び出すと、破棄のみが実行され、割り当て解除は実行されません。
したがって、デストラクタを明示的に呼び出す正当な使用法は、「オブジェクトを破棄したいだけですが、メモリ割り当てを解放しません(または解放できません)」ということです。
この一般的な例は、動的に割り当てる必要がある特定のオブジェクトのプールにメモリを事前に割り当てることです。
新しいオブジェクトを作成するときは、事前に割り当てられたプールからメモリのチャンクを取得し、「新規配置」を実行します。オブジェクトの処理が完了したら、デストラクタを明示的に呼び出して、クリーンアップ作業があれば終了することをお勧めします。ただし、演算子deleteが行ったように、実際にはメモリの割り当てを解除することはありません。代わりに、チャンクをプールに戻して再利用します。
FAQで引用されているように、placement 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つの機会を見つけました:
手動でデストラクタを呼び出す必要がある状況に遭遇したことはありません。Stroustrupでさえ、それは悪い習慣だと主張していることを覚えているようです。
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);
}
};
ctor
ここに書いた方法は間違っています。まさにあなたが自分で提供した理由です。リソースの割り当てが失敗した場合、クリーンアップに問題があります。'ctor'はを呼び出さないでくださいthis->~dtor()
。構築されたオブジェクトでdtor
呼び出す必要があります。この場合、オブジェクトはまだ構築されていません。何が起こっても、はクリーンアップを処理する必要があります。コード内では、何かがスローされた場合に自動クリーンアップを処理するようなutilsを使用する必要があります。自動クリーンアップをサポートするようにクラスのフィールドを変更することも良い考えです。ctor
ctor
std::unique_ptr
HANDLE h1, h2
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
ます。
cleanupGuard1
とcleanupGuard2
は、関連するxxxToCreate
リターンを実行するものと、関連するパラメータを実行するものに依存しますxxxxToDestroy
。それらが単純な場合は、何も書く必要がないかもしれませんstd::unique_ptr<x,deleter()>
。どちらの場合も(または同様の)トリックを実行できることがよくあるからです。
デストラクタを手動で呼び出す必要がある別の例を見つけました。いくつかのタイプのデータの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.
}
};
cleanup()
この場合とデストラクタで呼び出される特別なメソッドを宣言した場合、見た目はすっきりしませんか?