#include <iostream>
using namespace std;
struct A
{
A() { cout << "A" << endl; }
~A() { cout << "~A" << endl; }
};
A Ok() { return {}; }
A NotOk() { throw "NotOk"; }
struct B
{
A a1;
A a2;
};
void f(B) {}
int main()
{
try
{
f({ Ok(), NotOk() });
}
catch (...)
{}
}
vc++
およびclang
出力:
A
~A
gcc
出力中:
A
GCCの深刻なバグのようです。
参考として、 GCCバグ66139 および AndrzejKrzemieński による「GCCの重大なバグ」を参照してください。
私はただ疑問に思います:
C++標準は、均一な初期化が例外セーフであることを保証しますか?
そのようです:
不思議なことに、すべての場所の6.6/2ジャンプステートメント[stmt.jump]にあります(N4618):
スコープからの出口で(達成された場合)、そのスコープで作成された自動ストレージ期間(3.7.3)のオブジェクトは、作成とは逆の順序で破棄されます。 [注:一時的なものについては、12.2を参照してください。 —end note]ループ外、ブロック外、または自動ストレージ期間を持つ初期化された変数を越えて転送すると、転送されたポイントではなく、転送されたポイントではスコープ内にある自動ストレージ期間のオブジェクトが破棄されます。 。 (ブロックへの転送については6.7を参照してください)。 [注:ただし、プログラムは(たとえば、
std::exit()
またはstd::abort()
(18.5)を呼び出すことによって)自動ストレージ期間を持つクラスオブジェクトを破棄せずに終了できます。 —エンドノート]
ここでの重点は「(達成された)」部分にあると思います。これには例外が含まれます(ただし、std::terminate
)。
より良いリファレンスは§15.2/ 3コンストラクタとデストラクタ[except.ctor](emphasis mine)だと思います:
initializationまたは委任コンストラクター以外のオブジェクトの破棄が例外によって終了した場合、-オブジェクトの直接のサブオブジェクトごとにデストラクターが呼び出されます、および完全なオブジェクトの場合、仮想ベースクラスサブオブジェクト。初期化が完了(8.6)し、デストラクタがまだ実行を開始していない。ただし、破棄の場合は、union-likeクラスのバリアントメンバーは破棄されない。サブオブジェクトは、構築の完了とは逆の順序で破棄されます。このような破棄は、コンストラクターまたはデストラクターのfunction-try-blockのハンドラーに入る前にシーケンスされます。
これには、集約の初期化が含まれます(今日学習したものは非空の初期化と呼ぶことができます)
...そしてコンストラクタを持つオブジェクトについては、§12.6.2/ 12 [class.base.init](emphasis mine)を引用できます:
非委任コンストラクターでは、クラス型の潜在的に構築された各サブオブジェクトのデストラクターが潜在的に呼び出されます(12.4)。 [注:この規定により、例外がスローされた場合に完全に構築されたサブオブジェクトに対してデストラクタを確実に呼び出すことができます(15.2)。 —エンドノート]