Authors: | Ville Voutilainen <ville.voutilainen@gmail.com> |
---|---|
Number: | N2952=09-0142 |
Date: | 2009-09-21 |
This paper proposes a facility for accessing exceptions during stack unwinding, allowing rethrowing exceptions during unwind, which facilitates eg. non-intrusive logging of exceptions.
Currently, throw; ([except.throw]/p8) and current_exception() ([propagation]/p7) can be used to access the "currently handled exception" ([except.handle]/p8); practically this means that they can be used within catch blocks, but not during stack unwinding. It's not possible to rethrow exceptions during stack unwind, or get a pointer to the in-flight exception with current_exception(). People often request such a feature, in order to be able to log an exception in the destructor of a logger object.
struct logger { logger(const char* file, const char* line) { impl_->log_entry(file, line); } ~logger() { if (uncaught_exception()) { try { throw; } catch (foo& e) { impl_->log_foo(e); } catch (bar& e) { impl_->log_bar(e); } catch (...) { impl_->log_unknown(e); } } impl_->log_exit(); } logger_impl* impl_; };
The usage is roughly something like
void f() { logger log(__FILE__, __LINE__); //whatever operations here that may throw }
So the logger is a RAII object that should be able to log function entry/exit, and preferably also any exceptions occurring. Currently this sort of logger can't be written because throw; can only be done within a catch block, and current_exception() will return a null exception_ptr if called outside a catch block.
For the bare minimum, allowing throw; during unwind would be enough. However, it's valuable to also allow access to an exception_ptr returned by current_exception() during unwind. Some examples follow:
~foo() { if (uncaught_exception()) { try { throw; // ok, an exception is in-flight } catch (...) { // ok, the exception does not exit the destructor } } } ~foo() { throw; // this causes terminate(), a destructor // may not exit with an exception if unwind is // caused by an exception. Causes terminate() also // if there's no exception in-flight. } ~foo() { if (uncaught_exception()) { try { throw; // ok, an exception is in-flight } catch (foo_exception&) { } // this may cause terminate() if the in-flight exception // is not of a type convertible to foo_exception& } } void f() { throw; // if invoked from a destructor during unwind, it is ok as long // as the destructor does not exit with an exception. Also ok // if invoked from a catch block. } void f() { if (uncaught_exception()) { throw; // if invoked from a destructor during unwind, it is ok as long // as the destructor does not exit with an exception. In a catch // block, the exception is not uncaught and the condition // is false. } } void f() { exception_ptr current = current_exception(); // will return non-null if // an exception is in-flight if (current) { throw; // if invoked from a destructor during unwind, it is ok as long // as the destructor does not exit with an exception. Also works // in a catch block. } } ~foo() { exception_ptr current = current_exception(); // will return non-null if // an exception is in-flight if (current) { try { throw; // ok, an exception is in-flight } catch (...) { // ok, the exception does not exit the destructor } } }
So, to summarize,
I don't propose specific drafting yet, the wording must be carefully thought in order to fit into the existing standardese about exception handling.
In addition to accessing exceptions during unwind, people have expressed desire to access a function return value during unwind. I'd like to get EWG feedback for something roughly like
exception_ptr current_return_value()
Where the return value would be wrapped in an exception_ptr and could be rethrown and caught in order to deduce the type of the return value. I don't think such an exception_ptr would have a similar lifetime to the ones returned by current_exception(), because I think it would be expensive to copy the return value for storing after the return value has been received at the call site. Nevertheless, I'd like to know what EWG thinks of such a facility. Currently people try to hack this sort of facility in assembler, which leads to non-portable code.