私はこれに対する明確な答えを見つけることができなかったと本当に信じられません...
new
演算子を使用して初期化された場合、C++クラスコンストラクターが例外をスローした後に割り当てられたメモリをどのように解放しますか。例えば。:
class Blah
{
public:
Blah()
{
throw "oops";
}
};
void main()
{
Blah* b = NULL;
try
{
b = new Blah();
}
catch (...)
{
// What now?
}
}
これを試したところ、catchブロックのb
はNULLです(これは理にかなっています)。
デバッグ中に、コンストラクターに到達する前に、コントロールがメモリ割り当てルーチンに入るのに気づきました。
これはMSDNWebサイトにあります これを確認しているようです :
Newを使用してC++クラスオブジェクトにメモリを割り当てる場合、メモリが割り当てられた後にオブジェクトのコンストラクタが呼び出されます。
したがって、ローカル変数b
が割り当てられない(つまり、catchブロックでNULLである)ことを念頭に置いて、割り当てられたメモリをどのように削除しますか?
これについてクロスプラットフォームの答えを得るのもいいでしょう。つまり、C++仕様は何と言っていますか?
明確化:クラスがメモリ自体をc'torに割り当ててから、スローする場合については話していません。そのような場合、d'torが呼び出されないことを感謝します。 [〜#〜] the [〜#〜]オブジェクト(私の場合はBlah
)を割り当てるために使用されるメモリについて話しています)。
同様の質問 ここ および ここ を参照する必要があります。基本的に、コンストラクターが例外をスローした場合、オブジェクト自体のメモリが再び解放されても安全です。ただし、コンストラクター中に他のメモリーが要求された場合は、例外を除いてコンストラクターを離れる前に、自分でそれを解放する必要があります。
あなたの質問に対して、WHOはメモリを削除します。答えは、new-operator(コンパイラによって生成される)の背後にあるコードです。コンストラクタを離れる例外を認識した場合は、クラスメンバーのすべてのデストラクタを呼び出し(コンストラクタコードを呼び出す前にすでに正常に構築されているため)、メモリを解放する必要があります(おそらく、デストラクタの呼び出しと一緒に再帰的に実行できます)。適切なdeleteを呼び出し、このクラス自体に割り当てられたメモリを解放します。次に、キャッチされた例外をコンストラクターからnewの呼び出し元に再スローする必要があります。もちろん、やらなければならない作業はもっとあるかもしれませんが、各コンパイラの実装次第であるため、頭からすべての詳細を引き出すことはできません。
コンストラクターが例外をスローしたためにオブジェクトが破棄を完了できない場合、最初に発生すること(これはコンストラクターの特別な処理の一部として発生します)は、構築されたすべてのメンバー変数が破棄されることです-例外が初期化子リストにスローされた場合、これは、初期化子が完了した要素のみが破棄されることを意味します。
次に、オブジェクトがnew
で割り当てられていた場合、適切な割り当て解除関数(_operator delete
_)が、_operator new
_に渡されたのと同じ追加の引数を使用して呼び出されます。たとえば、new (std::nothrow) SomethingThatThrows()
はoperator new (size_of_ob, nothrow)
でメモリを割り当て、SomethingThatThrows
の構築を試み、正常に構築されたメンバーを破棄してから、次の場合にoperator delete (ptr_to_obj, nothrow)
を呼び出します。例外が伝播されます-メモリをリークしません。
注意しなければならないのは、複数のオブジェクトを連続して割り当てることです。後のオブジェクトの1つがスローされた場合、前のオブジェクトは自動的に割り当て解除されません。これを回避する最善の方法は、スマートポインターを使用することです。これは、ローカルオブジェクトとして、スタックの巻き戻し中にデストラクタが呼び出され、デストラクタがメモリの割り当てを適切に解除するためです。
コンストラクタがスローすると、オブジェクトに割り当てられたメモリが自動的にシステムに返されます。
スローしたクラスのデストラクタは呼び出されないことに注意してください。
ただし、基本クラス(基本コンストラクターが完了している場合)のデストラクタも呼び出されます。
注意:
他のほとんどの人が指摘しているように、メンバーはクリーンアップが必要な場合があります。
完全に初期化されたメンバーにはデストラクタが呼び出されますが、所有しているRAWポインタメンバーがある場合(つまり、デストラクタで削除)、スローを実行する前にクリーンアップを行う必要があります(所有を使用しないもう1つの理由)クラス内のRAWポインタ)。
#include <iostream>
class Base
{
public:
Base() {std::cout << "Create Base\n";}
~Base() {std::cout << "Destroy Base\n";}
};
class Deriv: public Base
{
public:
Deriv(int x) {std::cout << "Create Deriv\n";if (x > 0) throw int(x);}
~Deriv() {std::cout << "Destroy Deriv\n";}
};
int main()
{
try
{
{
Deriv d0(0); // All constructors/Destructors called.
}
{
Deriv d1(1); // Base constructor and destructor called.
// Derived constructor called (not destructor)
}
}
catch(...)
{
throw;
// Also note here.
// If an exception escapes main it is implementation defined
// whether the stack is unwound. By catching in main() you force
// the stack to unwind to this point. If you can't handle re-throw
// so the system exception handling can provide the appropriate
// error handling (such as user messages).
}
}
C++ 2003標準5.3.4/17から-新規:
上記のオブジェクト初期化のいずれかの部分が例外をスローして終了し、適切な割り当て解除関数が見つかった場合、割り当て解除関数が呼び出されて、オブジェクトが構築されていたメモリが解放されます。その後、例外はコンテキスト内で伝播し続けます。新しい式の。明確に一致する割り当て解除関数が見つからない場合、例外を伝播してもオブジェクトのメモリは解放されません。 [注:これは、呼び出された割り当て関数がメモリを割り当てない場合に適しています。そうしないと、メモリリークが発生する可能性があります。 ]
したがって、リークがある場合とない場合があります-適切なデアロケーターが見つかるかどうかによって異なります(オペレーターのnew/deleteがオーバーライドされていない限り、通常はそうです)。適切なデロケーターがある場合は、コンパイラーが責任を負います。コンストラクターがスローした場合の呼び出しでの配線用。
これは、コンストラクターで取得されたリソースに何が起こるかとはほとんど関係がないことに注意してください。これは、私の最初の回答の試みで説明されたものであり、多くのFAQ、記事、および投稿で説明されている質問です。
その長所と短所は、オブジェクト内で他のエンティティの割り当てを行っていない場合(例のように)、割り当てられたメモリが自動的に削除されることです。ただし、新しいステートメント(またはメモリを直接管理するその他のもの)は、コンストラクターのcatchステートメントで処理する必要があります。そうしないと、オブジェクトは後続の割り当てを削除せずに削除され、私の友人であるあなたにリークが発生します。