web-dev-qa-db-ja.com

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

::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つあります。オーバーロードされたMyClassoperator deleteを呼び出す前の1つ。グローバルoperator deleteを呼び出す前にもう1つ。

16
expinc

operator newoperator 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 )ただし、主要なコンパイラはこれについてのみ警告し、エラーではないことを決定したようです。不正な形式になる前は、::deletevoid*を使用すると、動作が未定義でした この質問 を参照してください。

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


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

17
walnut

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

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

return ::operator new(size);

同様に、クラス固有のoperator delete、 行う:

::operator delete(p);

operator new 詳細についてはリファレンスページ。

13

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

operator deleteoperator delete[]

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

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

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

1
Mario The Spoon