#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++
and clang
output:
A ~A
While gcc
outputs:
A
It seems a serious bug of GCC.
For reference, see GCC bug 66139 and "A serious bug in GCC" by Andrzej Krzemieński.
I just wonder:
Does the C++ standard guarantee that uniform initialization is exception-safe?
It seems so:
Curiously found in §6.6/2 Jump Statements [stmt.jump] of all places (N4618):
On exit from a scope (however accomplished), objects with automatic storage duration (3.7.3) that have been constructed in that scope are destroyed in the reverse order of their construction. [ Note: For temporaries, see 12.2. —end note ] Transfer out of a loop, out of a block, or back past an initialized variable with automatic storage duration involves the destruction of objects with automatic storage duration that are in scope at the point transferred from but not at the point transferred to. (See 6.7 for transfers into blocks). [ Note: However, the program can be terminated (by calling
std::exit()
orstd::abort()
(18.5), for example) without destroying class objects with automatic storage duration. —end note ]
I think the emphasis here is on the "(however accomplished)" part. This includes an exception (but excludes things that cause a std::terminate
).
I think a better reference is §15.2/3 Constructors and destructors [except.ctor] (emphasis mine):
If the initialization or destruction of an object other than by delegating constructor is terminated by an exception, the destructor is invoked for each of the object’s direct subobjects and, for a complete object, virtual base class subobjects, whose initialization has completed (8.6) and whose destructor has not yet begun execution, except that in the case of destruction, the variant members of a union-like class are not destroyed. The subobjects are destroyed in the reverse order of the completion of their construction. Such destruction is sequenced before entering a handler of the function-try-block of the constructor or destructor, if any.
This would include aggregate initialization (which I learned today can be called non-vacuous initialization)
...and for objects with constructors we can cite §12.6.2/12 [class.base.init](emphasis mine):
In a non-delegating constructor, the destructor for each potentially constructed subobject of class type is potentially invoked (12.4). [ Note: This provision ensures that destructors can be called for fully-constructed subobjects in case an exception is thrown (15.2). —end note ]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With