動的に割り当てられた10個の要素の配列へのポインターがあるとします。
T* p = new T[10];
後で、その配列を解放したいと思います。
delete[] p;
T
デストラクタの1つが例外をスローした場合はどうなりますか?他の要素はまだ破壊されていますか?メモリは解放されますか?例外は伝播されますか、それともプログラムの実行は終了しますか?
同様に、astd::vector<T>
が破壊され、T
破壊者の1人が投げるとどうなりますか?
動的に割り当てられた10個の要素の配列へのポインターがあるとします。
T* p = new T[10];
後で、その配列を解放したいと思います。
delete[] p;
T
デストラクタの1つが例外をスローした場合はどうなりますか?他の要素はまだ破壊されていますか?メモリは解放されますか?例外は伝播されますか、それともプログラムの実行は終了しますか?
同様に、astd::vector<T>
が破壊され、T
破壊者の1人が投げるとどうなりますか?
標準で明示的に呼び出されているのを見ることができません:
作成の逆順で呼び出されるだけです
6 delete-expression のオペランドの値が NULL ポインター値でない場合、delete-expression は、削除されるオブジェクトまたは配列の要素のデストラクタ (存在する場合) を呼び出します。配列の場合、要素はアドレスの降順に破棄されます(つまり、コンストラクターの完了の逆順です。12.6.2 を参照してください)。
また、例外がスローされた場合でも、メモリの割り当て解除が行われます。
7 delete-expression のオペランドの値が null ポインター値でない場合、delete-expression は解放関数 (3.7.4.2) を呼び出します。それ以外の場合、割り当て解除関数が呼び出されるかどうかは未指定です。[注: 割り当て解除関数は、オブジェクトまたは配列の一部の要素のデストラクタが例外をスローするかどうかに関係なく呼び出されます。— エンドノート]
G++ で次のコードを試してみたところ、例外の後にデストラクタが呼び出されないことがわかりました。
#include <iostream>
int id = 0;
class X
{
public:
X() { me = id++; std::cout << "C: Start" << me << "\n";}
~X() { std::cout << "C: Done " << me << "\n";
if (me == 5) {throw int(1);}
}
private:
int me;
};
int main()
{
try
{
X data[10];
}
catch(...)
{
std::cout << "Finished\n";
}
}
実行する:
> g++ de.cpp
> ./a.out
C: Start0
C: Start1
C: Start2
C: Start3
C: Start4
C: Start5
C: Start6
C: Start7
C: Start8
C: Start9
C: Done 9
C: Done 8
C: Done 7
C: Done 6
C: Done 5
Finished
これはすべてこれにつながります(非常に古い答え):
デストラクタから例外をスローする
絶対にしないでください。すでにアクティブな例外がある場合は、「Bang、you'redead」std::terminate
と呼ばれます。あなたのデストラクタはしなければなりません。いいえ。投げる。抵抗。
編集:標準(14882 2003)、15.2コンストラクタおよびデストラクタの関連セクション[except.dtor]
:
15.2.3 tryブロックからthrow-expressionへのパス上に構築された自動オブジェクトのデストラクタを呼び出すプロセスは、「スタックアンワインド」と呼ばれます。[注:スタックの巻き戻し中に呼び出されたデストラクタが例外を除いて終了した場合、terminateが呼び出されます(15.5.1)。したがって、デストラクタは通常、例外をキャッチし、デストラクタから伝播させないようにする必要があります。—エンドノート]
遊んでみるためのテストケース(実際には、派生したものをスローしstd::exception
、intなどをスローしないでください!):
#include <iostream>
int main() {
struct Foo {
~Foo() {
throw 0; // ... fore, std::terminate is called.
}
};
try {
Foo f;
throw 0; // First one, will be the active exception once Foo::~Foo()
// is executed, there- ...
} catch (int) {
std::cout << "caught something" << std::endl;
}
}
5.3.5.7 削除式のオペランドの値が NULL ポインター値でない場合、削除式は割り当て解除関数 (3.7.3.2) を呼び出します。それ以外の場合、解放関数が呼び出されるかどうかは不明です。[ 注: 割り当て解除関数は、オブジェクトまたは配列の一部の要素のデストラクタが例外をスローするかどうかに関係なく呼び出されます。— エンドノート]
を除いて、デストラクタについて何も見つかりませんでした
配列の場合、要素はアドレスの降順に破棄されます (つまり、コンストラクターの完了の逆順です。12.6.2 を参照してください)。
スローした後、デストラクタは呼び出されないと思いますが、よくわかりません。
2番目の質問に答えるために、代わりにstd :: vectorを使用した場合、deleteを呼び出す必要はなく、ポインターを使用していません(vectorクラスは内部的には信じていますが、これはあなた次第ではありません)管理する)。
さて、ここにいくつかの実験的なコードがあります:
#include <cstddef>
#include <cstdlib>
#include <new>
#include <iostream>
void* operator new[](size_t size) throw (std::bad_alloc)
{
std::cout << "allocating " << size << " bytes" << std::endl;
return malloc(size);
}
void operator delete[](void* payload) throw ()
{
std::cout << "releasing memory at " << payload << std::endl;
free(payload);
}
struct X
{
bool throw_during_destruction;
~X()
{
std::cout << "destructing " << (void*)this << std::endl;
if (throw_during_destruction) throw 42;
}
};
int main()
{
X* p = new X[10]();
p[5].throw_during_destruction = true;
p[1].throw_during_destruction = true;
delete[] p;
}
コードを実行すると、g++ 4.6.0 で次の出力が得られました。
allocating 14 bytes
destructing 0x3e2475
destructing 0x3e2474
destructing 0x3e2473
destructing 0x3e2472
destructing 0x3e2471
terminate called after throwing an instance of 'int'
This application has requested the Runtime to terminate it in an unusual way.
Please contact the application's support team for more information.
したがってstd::terminate
、最初のデストラクタがスローされるとすぐに呼び出されるように見えます。他の要素は破壊されず、メモリは解放されません。誰でもこれを確認できますか?
例外がスローされる場合は、スローされます。破壊に失敗したオブジェクトは明らかに適切に破壊されておらず、配列に残っているオブジェクトも破壊されていません。
ベクトルを使用する場合、問題は同じですが、コード内ではありません。:-)
したがって、デストラクタをスローすることは単なる悪い考え(tm)です。
@Martin が以下に示すように、スローされたオブジェクトは、デストラクタに入るとすぐに正式には存在しません。他の人も同様に記憶を取り戻しているかもしれません。
ただし、フラッシャーが適切にフラッシュされていない複雑なものがいくつか含まれていることは明らかでした。そのオブジェクトと、配列内でそれに続く他のオブジェクトにミューテックス ロック、開いているファイル、データベース キャッシュ、または shared_ptrs が含まれていて、それらのいずれにもデストラクタが実行されていない場合、大きな問題が発生する可能性があります。
その時点で std::terminate を呼び出して、プログラムを悲惨な状況から解放することは、あなたが望むもののように思えます!