::delete
のクラスのoperator delete
を呼び出そうとしました。しかし、デストラクタは呼び出されません。
operator delete
がオーバーロードされているクラスMyClass
を定義しました。グローバルoperator delete
もオーバーロードされています。 MyClass
のオーバーロードされたoperator delete
は、オーバーロードされたグローバルoperator delete
を呼び出します。
class MyClass
{
public:
MyClass() { printf("Constructing MyClass...\n"); }
virtual ~MyClass() { printf("Destroying MyClass...\n"); }
void* operator new(size_t size)
{
printf("Newing MyClass...\n");
void* p = ::new MyClass();
printf("End of newing MyClass...\n");
return p;
}
void operator delete(void* p)
{
printf("Deleting MyClass...\n");
::delete p; // Why is the destructor not called here?
printf("End of deleting MyClass...\n");
}
};
void* operator new(size_t size)
{
printf("Global newing...\n");
return malloc(size);
}
void operator delete(void* p)
{
printf("Global deleting...\n");
free(p);
}
int main(int argc, char** argv)
{
MyClass* myClass = new MyClass();
delete myClass;
return EXIT_SUCCESS;
}
出力は次のとおりです。
Newing MyClass...
Global newing...
Constructing MyClass...
End of newing MyClass...
Constructing MyClass...
Destroying MyClass...
Deleting MyClass...
Global deleting...
End of deleting MyClass...
実際:
MyClass
のオーバーロードされたoperator delete
を呼び出す前に、デストラクタへの呼び出しは1つだけです。
期待される:
デストラクタへの呼び出しは2つあります。オーバーロードされたMyClass
のoperator delete
を呼び出す前の1つ。グローバルoperator delete
を呼び出す前にもう1つ。
operator new
とoperator delete
を誤用しています。これらの演算子は、割り当ておよび割り当て解除関数です。それらはオブジェクトを構築または破壊する責任はありません。それらは、オブジェクトが配置されるメモリを提供することに対してのみ責任があります。
これらの関数のグローバルバージョンは、::operator new
および::operator delete
です。 ::new
と::delete
は新しい/削除式であり、new
/delete
とは異なり、::new
と::delete
はクラス固有のoperator new
/operator delete
オーバーロードをバイパスします。
New/delete-expressions構文/破壊および割り当て/割り当て解除(適切なoperator new
またはoperator delete
を構築前または破棄後に呼び出すことにより)。
オーバーロードは割り当て/割り当て解除の部分のみを担当するため、::operator new
と::operator delete
ではなく::new
と::delete
を呼び出す必要があります。
delete myClass;
のdelete
は、デストラクタの呼び出しを担当します。
p
には::delete p;
型があり、式はどのデストラクタを呼び出すかを認識できないため、void*
はデストラクタを呼び出しません。 delete-expressionへのオペランドとして::operator delete
を使用するのは不適切ですが、置き換えられたvoid*
を呼び出してメモリの割り当てを解除します(以下の編集を参照)。
::new MyClass();
は、置き換えられた::operator new
を呼び出してメモリを割り当て、その中にオブジェクトを構築します。このオブジェクトへのポインタはvoid*
としてMyClass* myClass = new MyClass();
のnew-expressionに返されます。これにより、このメモリにanother objectが作成され、前のオブジェクトの存続期間は終了しませんデストラクタ。
編集:
質問に対する@ M.Mのコメントのおかげで、void*
のオペランドとしての::delete
は実際には不正な形式であることがわかりました。 ( [expr.delete]/1 )ただし、主要なコンパイラはこれについてのみ警告し、エラーではないことを決定したようです。不正な形式になる前は、::delete
でvoid*
を使用すると、動作が未定義でした この質問 を参照してください。
したがって、プログラムの形式が正しくなく、コードがまだコンパイルできたとしても、コードが実際に上記で説明したことを実行する保証はありません。
彼の回答の下の@SanderDeDyckerで指摘されているように、オブジェクトのデストラクタを最初に呼び出さずにMyClass
オブジェクトをすでに含むメモリ内に別のオブジェクトを作成すると、違反するため、未定義の動作も発生します [basic.life]/5 プログラムがデストラクタの副作用に依存している場合、これを禁止します。この場合、デストラクタのprintf
ステートメントにはそのような副作用があります。
クラス固有のオーバーロードが正しく行われていません。これは出力で確認できます。コンストラクタが2回呼び出されます!
クラス固有のoperator new
、グローバル演算子を直接呼び出します:
return ::operator new(size);
同様に、クラス固有のoperator delete
、 行う:
::operator delete(p);
operator new
詳細についてはリファレンスページ。
CPPリファレンス を参照してください:
operator delete
、operator delete[]
一致する
operator new
によって以前に割り当てられたストレージの割り当てを解除します。これらの割り当て解除関数は、動的ストレージ期間を持つオブジェクトを破棄(または構築に失敗)した後、メモリの割り当てを解除するために、delete-expressionsおよびnew-expressionsによって呼び出されます。通常の関数呼び出し構文を使用して呼び出すこともできます。
削除(および新規)は、「メモリ管理」の部分のみを担当します。
したがって、デストラクタが1回だけ呼び出されることは明らかであり、オブジェクトのインスタンスをクリーンアップするために呼び出されます。それが2回呼び出された場合、すべてのデストラクタはそれがすでに呼び出されたかどうかを確認する必要があります。