Why Doesn't C++ Use Std::Nested_Exception to Allow Throwing from Destructor

Why doesn't C++ use std::nested_exception to allow throwing from destructor?

The problem you cite happens when your destructor is being executed as part of the stack unwinding process (when your object was not created as part of stack unwinding)1, and your destructor needs to emit an exception.

So how does that work? You have two exceptions in play. Exception X is the one that's causing the stack to unwind. Exception Y is the one that the destructor wants to throw. nested_exception can only hold one of them.

So maybe you have exception Y contain a nested_exception (or maybe just an exception_ptr). So... how do you deal with that at the catch site?

If you catch Y, and it happens to have some embedded X, how do you get it? Remember: exception_ptr is type-erased; aside from passing it around, the only thing you can do with it is rethrow it. So should people be doing this:

catch(Y &e)
{
if(e.has_nested())
{
try
{
e.rethrow_nested();
}
catch(X &e2)
{
}
}
}

I don't see a lot of people doing that. Especially since there would be an exceedingly large number of possible X-es.

1: Please do not use std::uncaught_exception() == true to detect this case. It is extremely flawed.

Why I couldn't handle an exception thrown from a destructor outside of it?

I think you misunderstood the recommendation:

a destructor usually should not throw an exception like STL containers and if it should, that thrown expression should be wrapped in a try-catch block (a catch must handle that exception).

In other words, it says:

You should not throw an exception from a destructor.

Now the question arises what one should do if the destructor is:

~Bar() {  
do_something();
}

and do_something() might throw an exception. In this case the recommendation is to catch the exception and handle it:

~Bar() {
try {
do_something();
} catch(...) {
// handle exception, eg write a log message
// but do not retrow it or a different one!
}
}

Now there is no way the exception leaves the destructor.


As you discovered, catching the exception outside of the destructor does not help to solve the general problem, because here:

 try{
Foo f{};
Foo* pFoo = new Foo{};
delete pFoo; // normally exception caught here nad handled by the following catch
}

The call to delete pFoo; throws an exception. During stack unwinding Foo f{}; will be destroyed and another exception will be thrown from the destructor. If an exception is thrown during stack-unwinding std::terminate will be called. And the way to avoid that to happen is to not throw exceptions from destructors.

Using RAII to nest exceptions

In the absence of a method to catch (and consume) the uncaught exception in the destructor, there is no way to rethrow an exception, nested or not, in the context of the destructor without std::terminate being called (when the exception is thrown in the context of exception handling).

std::current_exception (combined with std::rethrow_exception) will only return a pointer to a currently handled exception. This precludes its use from this scenario as the exception in this case is explicitly unhandled.

Given the above, the only answer to give is from an aesthetic perspective. Function level try blocks make this look slightly less ugly. (adjust for your style preference):

void foo() try {
// code that might throw
std::ifstream file("nonexistent.file");
file.exceptions(std::ios_base::failbit);
}
catch(...) {
std::throw_with_nested(std::runtime_error("foo failed"));
}

libstdc++ std::throw_with_nested() requires polymorphic types

N3337 [except.nested]/5,6

[[noreturn]] template <class T> void throw_with_nested(T&& t);

Let U be remove_reference<T>::type.
5. Requires: U shall be
CopyConstructible.
6. Throws: if U is a non-union class type not
derived from nested_exception, an exception of un-specified type that is publicly derived from both U and nested_exception and constructed from std::forward<T>(t), otherwise std::forward<T>(t).

Also consider that nested_exception is polymorphic as it has a virtual destructor ([except.nested]/2):

[ Note: nested_exception has a virtual destructor to make it a
polymorphic class. Its presence can be tested for with dynamic_cast.
— end note ]

The exception-type that is actually thrown is always polymorphic. U doesn't have to be, though - just CopyConstructible, as noted by the Requires-section.

So libstdc++ has an invalid implementation. It should internally specialize for types for which is_base_of<nested_exception, U>::value is false.

c++ exception : throwing std::string

Yes. std::exception is the base exception class in the C++ standard library. You may want to avoid using strings as exception classes because they themselves can throw an exception during use. If that happens, then where will you be?

boost has an excellent document on good style for exceptions and error handling. It's worth a read.

Should exceptions be chained in C++?

It is necessary to copy the data out of an exception object, into a chain, if you want it to outlive the catch block that receives it, aside from rethrow by throw;. (Which includes, for example, if that catch block exits through a throw obj;.)

This can be done by putting data to be saved on the heap, and implementing swap (move in C++0x) on your private data inside the exception, for example.

Of course, you need to be careful when using the heap with exceptions… but then again, in most modern OSes, memory overcommitment completely prevents new from ever throwing, for better or for worse. A good memory margin and dropping exceptions from the chain upon complete meltdown should keep it safe.

struct exception_data { // abstract base class; may contain anything
virtual ~exception_data() {}
};

struct chained_exception : std::exception {
chained_exception( std::string const &s, exception_data *d = NULL )
: data(d), descr(s) {
try {
link = new chained_exception;
throw;
} catch ( chained_exception &prev ) {
swap( *link, prev );
} // catch std::bad_alloc somehow...
}

friend void swap( chained_exception &lhs, chained_exception &rhs ) {
std::swap( lhs.link, rhs.link );
std::swap( lhs.data, rhs.data );
swap( lhs.descr, rhs.descr );
}

virtual char const *what() const throw() { return descr.c_str(); }

virtual ~chained_exception() throw() {
if ( link && link->link ) delete link; // do not delete terminator
delete data;
}

chained_exception *link; // always on heap
exception_data *data; // always on heap
std::string descr; // keeps data on heap

private:
chained_exception() : link(), data() {}
friend int main();
};

void f() {
try {
throw chained_exception( "humbug!" );
} catch ( std::exception & ) {
try {
throw chained_exception( "bah" );
} catch ( chained_exception &e ) {
chained_exception *ep = &e;
for ( chained_exception *ep = &e; ep->link; ep = ep->link ) {
std::cerr << ep->what() << std::endl;
}
}
}

try {
throw chained_exception( "meh!" );
} catch ( chained_exception &e ) {
for ( chained_exception *ep = &e; ep->link; ep = ep->link ) {
std::cerr << ep->what() << std::endl;
}
}
}

int main() try {
throw chained_exception(); // create dummy end-of-chain
} catch( chained_exception & ) {
// body of main goes here
f();
}

output (appropriately grumpy):

bah
humbug!
meh!

RAII way to get errors that are caught during destruction

You might partially handle the case of failure in destructor:

class Foo {
public:
Foo() : count(std::uncaught_exceptions()) {}
~Foo() noexcept(false)
{
if (std::uncaught_exceptions() != count) {
// ~Foo() called during stack unwinding
// Cannot throw exception safely.
} else {
// ~Foo() called normally
// Can throw exception
}
}
private:
int count;
};

How to throw a C++ exception

Simple:

#include <stdexcept>

int compare( int a, int b ) {
if ( a < 0 || b < 0 ) {
throw std::invalid_argument( "received negative value" );
}
}

The Standard Library comes with a nice collection of built-in exception objects you can throw. Keep in mind that you should always throw by value and catch by reference:

try {
compare( -1, 3 );
}
catch( const std::invalid_argument& e ) {
// do stuff with exception...
}

You can have multiple catch() statements after each try, so you can handle different exception types separately if you want.

You can also re-throw exceptions:

catch( const std::invalid_argument& e ) {
// do something

// let someone higher up the call stack handle it if they want
throw;
}

And to catch exceptions regardless of type:

catch( ... ) { };

I want to catch an exception and bundle it within my own exception and throw upwards

How about a nested exception?

try { /* ... */ }
catch (...)
{
throw MyException("An error occurred", std::current_exception());
}

Just make a suitable class that stores the exception:

struct MyException : std::exception
{
std::string message;
std::exception_ptr nested_exception;

MyException(std::string m, std::exception_ptr e)
: message(std::move(m))
, nested_exception(std::move(e))
{ }

// ...
};

When the exception is caught, the catcher can rethrow the nested exception:

try { /* load resource */ }
catch (MyException & e)
{
log("Resource loading failed: " + e.what());
std::rethrow_exception(e.nested_exception);
}

In fact, this entire logic is already provided by the standard library via std::throw_with_nested.



Related Topics



Leave a reply



Submit