演算子の削除でデストラクタが呼び出されないのはなぜですか?


16

私はそれに::deleteクラスを呼び込もうとしましたoperator delete。しかし、デストラクタは呼び出されません。

私は、クラス定義されMyClass、そのoperator deleteオーバーロードされているが。グローバルoperator deleteもオーバーロードされています。のオーバーロードoperator deleteMyClass、オーバーロードされたグローバルを呼び出します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...

実際:

のオーバーロードoperator deleteを呼び出す前に、デストラクターへの呼び出しは1つだけですMyClass

期待される:

デストラクタへの呼び出しは2つあります。のオーバーロードoperator deleteを呼び出す前の1つMyClass。グローバルを呼び出す前の別のoperator delete


6
MyClass::operator new()(少なくとも)sizeバイトのrawメモリを割り当てる必要があります。のインスタンスを完全に構築しようとするべきではありませんMyClass。のコンストラクタMyClassはの後に実行されMyClass::operator new()ます。次に、delete式in main()がデストラクタを呼び出し、メモリを解放します(デストラクタを再度呼び出すことはありません)。::delete p発現は、オブジェクトの種類に関する情報がありませんpので、点をpあるvoid *ので、デストラクタを呼び出すことはできません。
Peter


2
すでにあなたに与えられた応答は正しいですが、私は疑問に思います:なぜあなたは新しいものを上書きして削除しようとしているのですか?典型的な使用例は、カスタムメモリ管理(GC、デフォルトのmalloc()に由来しないメモリなど)の実装です。たぶん、あなたが達成しようとしていることに間違ったツールを使用しているのかもしれません。
noamtm

2
::delete p;*pのタイプが削除されるオブジェクトのタイプと同じではないため(または仮想デストラクタを持つ基本クラスではない)、未定義の動作が発生します
MM

@MM主要なコンパイラはせいぜい警告するだけなのでvoid*、オペランドが明示的に不正な形式であることさえ知らなかった。[expr.delete] / 1: " オペランドは、オブジェクトタイプまたはクラスタイプへのポインタでなければなりません。[...]これは、voidがオブジェクトタイプではないため、タイプvoidのポインタを使用してオブジェクトを削除できないことを意味します。 * "@OP回答を修正しました。
ウォールナット、

回答:


17

あなたは誤用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 newandの::operator delete代わりに::newandを呼び出す必要があり::deleteます。

delete中にはdelete myClass;デストラクタを呼び出すための責任があります。

::delete p;にはp型があるvoid*ため、式はデストラクタを呼び出す必要がないため、デストラクタを呼び出しません。おそらく、置換さ::operator deleteれたものを呼び出してメモリの割り当てを解除しますvoid*が、delete式に asオペランドを使用するのは不適切です(以下の編集を参照)。

::new MyClass();置き換えられたもの::operator newを呼び出してメモリを割り当て、その中にオブジェクトを構築します。このオブジェクトへのポインタが返されるようvoid*に新たな表現にMyClass* myClass = new MyClass();続い構築します、別のオブジェクトをそのデストラクタを呼び出すことなく、前のオブジェクトの有効期間を終了し、このメモリに。


編集:

質問に対する@MMのコメントのおかげで、私はvoid*toのオペランド::deleteが実際には不正な形式であることに気付きました。([expr.delete] / 1)ただし、主要なコンパイラはこれについてのみ警告し、エラーではないことを決定したようです。正しく定義されておらず、すでに定義さ::deletevoid*ていない動作を使用する前に、この質問を参照してください。

したがって、プログラムの形式が正しくなく、コードが実際にコンパイルできた場合、コードが実際に上記で説明したことを実行する保証はありません。


彼の回答の下にある@SanderDeDyckerで指摘されているように、MyClassオブジェクトのデストラクタを最初に呼び出さずにオブジェクトを既に含むメモリ内に別のオブジェクトを構築することにより、[basic.life] / 5に違反し、プログラムはデストラクタの副作用に依存します。この場合、printfデストラクタのステートメントにはそのような副作用があります。


誤用は、これらの演算子がどのように機能するかを確認することを目的としています。しかし、あなたの答えをありがとう。それが私の問題を解決する唯一の答えのようです。
expinc

13

クラス固有のオーバーロードが正しく行われていません。これは出力で確認できます。コンストラクタは2回呼び出されます!

クラス固有operator newので、グローバル演算子を直接呼び出します。

return ::operator new(size);

同様に、クラス固有operator deleteので、次のようにします。

::operator delete(p);

詳細については、operator newリファレンスページを参照してください。


:: new in operator newを呼び出すことでコンストラクターが2回呼び出されることを知っています。私の質問は、演算子の削除で:: deleteを呼び出すときにデストラクタが呼び出されないのはなぜですか?
expinc

1
@expinc:最初にデストラクタを呼び出さずに、意図的にコンストラクタをもう一度呼び出すのは非常に悪い考えです。自明ではないデストラクタ(たとえば、デストラクタ)の場合、未定義の動作領域に挑戦しています(デストラクタの副作用に依存している場合)-ref。[basic.life]§5。これを行わないでください。
Sander De Dycker

1

CPPリファレンスを参照してください。

operator deleteoperator delete[]

マッチングによって以前に割り当てられたストレージの割り当てを解除しますoperator new。これらの割り当て解除関数は、動的ストレージ期間を持つオブジェクトを破棄(または構築に失敗)した後、メモリの割り当てを解除するために、delete-expressionsおよびnew-expressionsによって呼び出されます。通常の関数呼び出し構文を使用して呼び出すこともできます。

削除(および新規)は、「メモリ管理」の部分のみを担当します。

そのため、デストラクタが1回だけ呼び出されること、つまりオブジェクトのインスタンスをクリーンアップするために呼び出されることは明らかです。それが2回呼び出される場合、すべてのデストラクタはそれがすでに呼び出されたかどうかを確認する必要があります。


1
インスタンスを削除した後のデストラクタを示す彼自身のログからわかるように、削除演算子は依然としてデストラクタを暗黙的に呼び出す必要があります。ここでの問題は、彼のクラス削除オーバーライドが:: deleteを呼び出しているため、グローバル削除オーバーライドが発生することです。このグローバル削除オーバーライドはメモリを解放するだけなので、デストラクタを再度呼び出すことはありません。
Pickle Rick、

参照は、削除がオブジェクトの解体後と呼ばれていることを明確に述べています
マリオザスプーン

はい、クラスの削除後にグローバル削除が呼び出されます。ここには2つのオーバーライドがあります。
Pickle Rick、

2
@PickleRick-削除がデストラクタ(デストラクタのある型へのポインタを指定した場合)またはデストラクタのセット(配列形式)を呼び出すことは事実ですが、operator delete()関数は削除式と同じではありません。デストラクタは、operator delete()関数が呼び出される前に呼び出されます。
ピーター

1
qouteにヘッダーを追加すると役立ちます。現在、それが何を指しているのか明確ではありません。「Dellocates storage ...」-誰がストレージの割り当てを解除しますか?
idclev 463035818
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.