The advice from the ScopeGuard article was
In the realm of exceptions, it is fundamental that you can do nothing if your "undo/recover" action fails. You attempt an undo operation, and you move on regardless whether the undo operation succeeds or not.
It may sound crazy, but consider:
- I manage to run out of memory and get a
std::bad_alloc
exception
- My clean-up code logs the error
- Unfortunately, the write fails (maybe the disk is full), and tries to throw an exception
Can I undo the log write? Should I try?
When an exception is thrown, all you really know is that the program is in an invalid state. You shouldn't be surprised that some impossible things turn out to be possible after all. Personally, I've seen far more cases where Alexandrescu's advice makes the most sense than otherwise: try to clean up, but recognize that the first exception means that things are already in an invalid state, so additional failures -- especially failures caused by the first problem ("error cascade") -- should not be a surprise. And trying to handle them is not going to end well.
I should probably mention that Cap'n Proto does exactly what you've proposed:
When Cap’n Proto code might throw an exception from a destructor, it first checks std::uncaught_exception()
to ensure that this is safe. If another exception is already active, the new exception is assumed to be a side-effect of the main exception, and is either silently swallowed or reported on a side channel.
But, as Yakk said, destructors became nothrow(true)
by default in C++11. Which means that if you want to do this, you need to be sure that in C++11 and later you mark the destructor as nothrow(false)
. Otherwise, throwing an exception from the destructor will terminate the program even if there is no other exception in flight. And note, "If another exception is already active, the new exception is assumed to be a side-effect of the main exception, and is either silently swallowed or reported on a side channel."