| ||||||||||
What is the standard function uncaught_exception(), and when should it be used? The answer given here isn't one that most people would expect.
What does std::uncaught_exception() do? Consider the following code: T::~T() { if( !std::uncaught_exception() ) { // ... code that could throw ... } else { // ... code that won't throw ... } } Is there any other good use for uncaught_exception? Discuss and draw conclusions.
What does std::uncaught_exception() do? It provides a way of knowing whether there is an exception currently active. (Note that this is not the same thing as knowing whether it is safe to throw an exception.) To quote directly from the standard (15.5.3/1):
As it turns out, this specification is deceptively close to being useful. Consider the following code: In short: No, even though it attempts to solve a problem. There are technical grounds why it shouldn't be used (i.e., it doesn't always work), but I'm much more interested in arguing against this idiom on moral grounds. If a destructor throws an exception, Bad Things can happen. Specifically, consider code like the following: // The problem // class X { public: ~X() { throw 1; } }; void f() { X x; throw 2; } // calls X::~X (which throws), then calls terminate()"Aha," many people -- including many experts -- have said, "let's use uncaught_exception() to figure out whether we can throw or not!" And that's where the code in Question 2 comes from... it's an attempt to solve the illustrated problem: // The wrong solution // T::~T() { if( !std::uncaught_exception() ) { // ... code that could throw ... } else { // ... code that won't throw ... } }One problem is that the above code won't actually work as expected in some situations. Consider: // Why the wrong solution is wrong // U::~U() { try { T t; // do work } catch( ... ) { // clean up } } // Variant: Another wrong solution // Transaction::~Transaction() { if( uncaught_exception() ) { RollBack(); } } // Variant: Why the wrong solution is still wrong // U::~U() { try { Transaction t( /*...*/ ); // do work } catch( ... ) { // clean up } }In my view, however, the "it doesn't work" problem isn't even the main issue here. My major problem with this solution is not technical, but moral: It is poor design to give T::~T() two different semantics, for the simple reason that it is always poor design to allow an operation to report the same error in two different ways. Not only does it complicate the interface and the semantics, but it makes the caller's life harder because the caller must be able to handle both flavours of error reporting -- and this when far too many programmers don't check errors well in the first place! The right answer to the problem is much simpler: // The right solution // T::~T() /* throw() */ { // ... code that won't throw ... } // Alternative right solution // T::Close() { // ... code that could throw ... } T::~T() /* throw() */ { try { Close(); } catch( ... ) { } } Is there any other good use for uncaught_exception? Discuss and draw conclusions.Unfortunately, I do not know of any good and safe use for std::uncaught_exception. My advice: Don't use it. |