26

プレースメント newを使用する場合は、デストラクタを手動で呼び出す必要があることを読んだことがあります。

次のコードを検討してください。

   // Allocate memory ourself
char* pMemory = new char[ sizeof(MyClass)];

// Construct the object ourself
MyClass* pMyClass = new( pMemory ) MyClass();

// The destruction of object is our duty.
pMyClass->~MyClass();

私の知る限り、オペレーターdeleteは通常、デストラクタを呼び出してからメモリの割り当てを解除しますよね? deleteでは、代わりに使用しないのはなぜですか?

delete pMyClass;  //what's wrong with that?

nullptr最初のケースでは、次のようにデストラクタを呼び出した後にpMyClass を強制的に設定します。

pMyClass->~MyClass();
pMyClass = nullptr;  // is that correct?

しかし、デストラクタはメモリの割り当てを解除しませんでしたよね? それで、それはメモリリークでしょうか?

私は混乱しています、あなたはそれを説明できますか?

4

4 に答える 4

41

式を使用すると、メモリを割り当てるnew関数が呼び出され、operator new新しい配置を使用してそのメモリ内にオブジェクトが作成されます。delete式はオブジェクトのデストラクタを呼び出し、次に を呼び出しますoperator delete。ええ、名前は紛らわしいです。

//normal version                   calls these two functions
MyClass* pMemory = new MyClass;    void* pMemory = operator new(sizeof(MyClass));
                                   MyClass* pMyClass = new( pMemory ) MyClass();
//normal version                   calls these two functions
delete pMemory;                    pMyClass->~MyClass();
                                   operator delete(pMemory);

あなたの場合、placement new を手動で使用したため、デストラクタも手動で呼び出す必要があります。メモリを手動で割り当てたので、手動で解放する必要があります。

ただし、placement new は、バッファーが で割り当てられていない内部バッファー (およびその他のシナリオ) でも機能するように設計されているため、それらoperator newを呼び出すべきではありませんoperator delete

#include <type_traits>

struct buffer_struct {
    std::aligned_storage_t<sizeof(MyClass), alignof(MyClass)> buffer;
};
int main() {
    buffer_struct a;
    MyClass* pMyClass = new (&a.buffer) MyClass(); //created inside buffer_struct a
    //stuff
    pMyClass->~MyClass(); //can't use delete, because there's no `new`.
    return 0;
}

このクラスの目的は、buffer_structどのような方法でもストレージを作成および破棄することmainですが、 の構築/破棄を処理MyClassします。この 2 つが (ほぼ*) 互いに完全に分離されていることに注意してください。

*ストレージが十分な大きさであることを確認する必要があります

于 2012-01-18T23:15:51.630 に答える
9

これが間違っている理由の 1 つ:

delete pMyClass;

配列であるため、削除pMemoryする必要があります。delete[]

delete[] pMemory;

上記の両方を行うことはできません。

malloc()同様に、メモリの割り当て、新しい配置、オブジェクトの作成、およびdeleteメモリの削除と解放に使用できない理由を尋ねるかもしれません。その理由は、malloc()andfree()ではなくmalloc()andに一致する必要があるからですdelete

現実の世界では、配置の新規および明示的なデストラクタ呼び出しはほとんど使用されません。それらは、標準ライブラリの実装 (またはコメントに記載されている他のシステムレベルのプログラミング) によって内部的に使用される可能性がありますが、通常のプログラマーはそれらを使用しません。私は長年 C++ を使用してきましたが、プロダクション コードにそのようなトリックを使用したことはありません。

于 2012-01-18T23:04:03.677 に答える
4

delete演算子とを区別する必要がありoperator deleteます。特に、placement new を使用している場合は、デストラクタを明示的に呼び出してからoperator delete(演算子ではなくdelete) 呼び出してメモリを解放します。つまり、

X *x = static_cast<X*>(::operator new(sizeof(X)));
new(x) X;
x->~X();
::operator delete(x);

これはoperator deletedelete演算子よりも低レベルであり、デストラクタを気にしない を使用することに注意してください (基本的に に少し似ていますfree)。deleteこれを、デストラクタを呼び出して を呼び出すのと同じことを内部的に行う演算子と比較してくださいoperator delete

::operator newバッファを使用したり、割り当てたり、割り当てを解除したりする必要がないことに注意してください::operator delete。新しい配置に関する限り、バッファがどのように生成/破棄されるかは問題ではありません。重要なポイントは、メモリ割り当てとオブジェクトの有効期間の問題を分離することです。

ちなみに、これの可能なアプリケーションはゲームのようなもので、メモリ使用量を慎重に管理するために事前に大きなメモリ ブロックを割り当てたい場合があります。次に、既に取得したメモリ内にオブジェクトを構築します。

別の可能な使用法は、最適化された小さな固定サイズのオブジェクト アロケーターです。

于 2012-01-18T23:38:27.933 に答える
3

1 つのメモリ ブロック内に複数のMyClass オブジェクトを構築することを想像すると、おそらく理解しやすいでしょう。

その場合、次のようになります。

  1. new char[10*sizeof(MyClass)] または malloc(10*sizeof(MyClass)) を使用して巨大なメモリ ブロックを割り当てます。
  2. そのメモリ内に 10 個の MyClass オブジェクトを作成するには、placement new を使用します。
  3. 何かをしてください。
  4. 各オブジェクトのデストラクタを呼び出します
  5. delete[] または free() を使用してメモリの大きなブロックの割り当てを解除します。

これは、コンパイラや OS などを作成している場合に行う可能性のあることです。

この場合、削除呼び出す理由がないため、「デストラクタ」と「削除」のステップを別々にする必要がある理由が明確になることを願っています。ただし、通常の方法でメモリの割り当てを解除する必要があります (解放、削除、巨大な静的配列に対しては何もしない、配列が別のオブジェクトの一部である場合は正常に終了するなど)。漏れます。

また、グレッグが言ったように、この場合、配列を new[] で割り当てたため、delete[] を使用する必要があるため、delete を使用できないことに注意してください。

また、MyClass の delete をオーバーライドしていないと仮定する必要があることにも注意してください。そうしないと、「new」とほぼ確実に互換性のない、まったく異なることが行われます。

ですから、あなたが説明したように「削除」と呼びたいとは思わないと思いますが、うまくいくでしょうか? これは基本的に、「デストラクタを持たない無関係な型が 2 つあります。一方の型へのポインタを新しく作成し、別の型へのポインタを介してそのメモリを削除できますか?」と同じ質問だと思います。言い換えれば、「私のコンパイラには、割り当てられたすべてのものの1つの大きなリストがありますか、それとも、さまざまなタイプに対してさまざまなことを行うことができますか」.

よくわかりません。それが言う仕様を読む:

5.3.5 ... [削除演算子の]オペランドの静的型が動的型と異なる場合、静的型はオペランドの動的型の基本クラスであり、静的型は仮想デストラクタまたは動作は未定義です。

それは「関係のない2つの型を使うとうまくいかない(仮想デストラクタでクラスオブジェクトをポリモーフィックに削除してもOK)」ということだと思います。

いいえ、そうしないでください。コンパイラが型ではなくアドレスのみを調べる場合 (どちらの型もアドレスを破壊する多重継承クラスではない場合)、実際にはうまくいくことが多いと思いますが、試してはいけません。

于 2012-01-19T00:06:03.703 に答える