Herb Sutter's discussion of std::uncaught_exception() concludes "Unfortunately, I do not know of any good and safe use for std::uncaught_exception. My advice: Don't use it."
The technical reason (aside from the moral objection) is the inability to differentiate between "this destructor was directly called by stack unwinding" and "stack unwinding is ongoing, but this destructor was called from a regular scope exit". However, sometimes exactly this differentiation would be helpful to perform additional cleanup specific to stack unwinding only. Example:
struct Transaction { Transaction(); ~Transaction() { if (std::uncaught_exception()) RollBack(); else Commit(); } };The well-intended meaning was for a transaction to roll back if its scope was exited via an exception. However, this detection is unreliable for the following usage:
U::~U() { try { Transaction t( /*...*/ ); // do work } catch( ... ) { // clean up } }If U::~U() is called during stack unwinding, the transaction inside will always roll back, although "commit" was expected. Note that the transaction construct could appear in a called function that is not (and should not be) aware of the context from which it is called.
struct S { ~S(); // regular destructor ~S(int); // unwinding destructor };Features:
s.~S()
struct Transaction { ~Transaction() { Commit(); } ~Transaction(int) { RollBack(); } };Open issues:
bool
parameter
conceptually replaces (not: complements) the existing destructor
syntax. A class can either have a traditional destructor without any
parameters, or one that takes a bool
parameter.
Example:
struct S { ~S(bool unwinding); // called with argument value "true" if unwinding };Features:
s.~S(true)
struct Transaction { ~Transaction(bool unwinding) { if (unwinding) RollBack(); else Commit(); } };Open issues:
struct S { unsigned int count = std::uncaught_exception_count(); ~S() { /* unwinding if count < std::uncaught_exception_count() */ } };Features:
struct Transaction { const unsigned int count = std::uncaught_exception_count(); ~Transaction() { if (count < std::uncaught_exception_count()) // unwinding RollBack(); else Commit(); } };The memory footprint can be made as small as one bit:
struct S { bool latch : 1; S() : latch(std::uncaught_exception_count() & 1) { } ~S() { /* unwinding if latch != std::uncaught_exception_count() & 1 */ } };
Existing code will continue to work as before; only new code expressly using the new feature will be affected. No additional keyword is consumed. The "int" parameter for the unwinding destructor is motivated by a roughly similar use for overloaded increment and decrement operators; see 13.5.7 over.inc.
The ~S(int) idea seems to be inferior, because the presence of an ~S(int) destructor changes the possible call contexts and thus the implied meaning of an already existing ~S() destructor. Factoring common code in the destructor becomes more involved. Also, the wording changes appear to be more involved.
The ~S(bool) idea seems to imply quite a few wording changes for a feature only used in fringe cases. The std::uncaught_exception_count() approach seems to localize and minimize the required changes.
The mechanisms presented above are only intended for objects with automatic storage duration. For static, thread, or dynamic storage duration, the semantics should be well-defined, but no particular add-on value should be expected from the new feature (regardless of approach).