Overview
The following discusses how to handle an exception, nested exceptions, uncaught and unexpected exceptions, termination. Also setting exception and termination handlers.
Details
When an exception is thrown, it's caught in the catch block and processed there. However in some multithreading scenarios, a dedicated thread might handle all exceptions and perform logging or reporting etc.
To support such scenarios, exception_ptr and other helper functions were introduced as discussed below.
exception_ptr
An exception_ptr is a smart pointer that refers to an exception object.
It behaves like a shared pointer. The pointed exception is guaranteed to remain valid for as long as at least one exception_ptr points to it, potentially extending its lifetime beyond the scope of a catch statement or across threads.
The pointed exception's destructor is called when the last exception_ptr referring to it goes out of scope.
An exception_ptr is contextually convertible to bool, and will evaluate to false if it is null, true otherwise.
The following helper functions are defined to work with the exception_ptr.
Name | Description |
---|---|
exception_ptr current_exception() | It's called in a catch block, after the exception is thrown to capture the current exception object and creates an exception_ptr that holds either a copy or a reference to that exception object (depending on the implementation). If it fails, it refers to bad_alloc or some other exception object depending on the error occurred during capture. The referenced object remains valid at least as long as there is an exception_ptr object that refers to it. If no exception is being handled, the function returns a null-pointer value. |
exception_ptr make_exception_ptr (E e) | Returns an exception_ptr object that points to a copy of e. This is similar to current_exception() except the exception itself is not thrown. The referenced object remains valid at least as long as there is an exception_ptr object that refers to it. |
void rethrow_exception (exception_ptr p) | Throws the exception object pointed by p. Note that this function is marked by attribute [[noreturn]]. Before calling this function, the caller should check if p is valid using its boolean conversion. |
An exception_ptr is a smart pointer that stores current exception by calling current_exception() in a catch block.
Once exception_ptr is created and transferred to its separate handler, the handler can call bool operator to check if the exception_ptr is valid. Further, the handler can call rethrow_exception() to get exception details for post processing.
The exception object referenced by an exception_ptr remains valid as long as there remains at least one exception_ptr that is referencing it.
The pointed exception's destructor is called when the last exception_ptr referring to it goes out of scope.
This example 3 below depicts this scenario.
void handle_eptr(exception_ptr eptr) { try { if (eptr) rethrow_exception(eptr); } catch(const exception& e) { cerr << "Caught exception: " << e.what() << endl; } } exception_ptr eptr; try { // this generates a out_of_range string().at(1); } catch(...) { // capture eptr = current_exception(); } /*prints Caught exception: basic_string::at: __n (which is 1) >= this->size() (which is 0) */ handle_eptr(eptr); // destructor for out_of_range called when the eptr is destructed
An exception_ptr can also be explicitly created by calling method make_exception().
This example 4 below depicts this scenario.
void handle_eptr(exception_ptr eptr) { try { if (eptr) rethrow_exception(eptr); } catch(const exception& e) { cerr << "Caught exception: " << e.what() << endl; } } exception_ptr divide(int m, int n) { exception_ptr eptr; if (n == 0) { ostringstream s; s << m << '/' << n; eptr = make_exception_ptr(domain_error("bad input:"+s.str())); } else cout << "result:" << (m/n) << endl; return eptr; } auto eptr = divide(1,0); /*prints Caught exception: bad input:1/0 */ handle_eptr(eptr); // destructor for domain_error called when the eptr is destructed
Sometimes exceptions needs to be cascaded. For example, a database layer of an application may need to preserve exceptions from the database server, and add its own exception on top of it.
nested_exception can store current exception as well as nest another exception. So a hierarchy of exceptions can be nested within.
nested_exception
The nested_exception class is defined as below:
class nested_exception{ public: nested_exception() noexcept; [[noreturn]] void rethrow_nested() const; exception_ptr nested_ptr() const noexcept; }
The nested_exception class is a mixin class that can store currently handled exception as nested in addition to another exception as current.
A nested_exception can be created from current exception by calling the constructor from the catch block.
The following describes nested_exception in detail.
Name | Description |
---|---|
nested_exception() | Stores an exception object obtained by calling current_exception() within the new nested_exception object. |
void rethrow_nested() | Throws the nested exception. If the nested exception is a null exception_ptr, the function calls terminate0 instead. |
exception_ptr nested_ptr() | Returns an exception_ptr object that points to the nested exception. This may be a null exception_ptr, if the nested_exception was constructed while not handling an exception. |
The following helper functions are defined to work with the nested_exception.
Name | Description |
---|---|
void throw_with_nested (T&& e) | Constructs an Object with the currently handled exception becomes the nested exception and e the outer exception. The object type is publicly derived both from T and from the currently handled exception. The object has the same properties and members as the outer exception, but carry additional information related to the nested exception and includes two member functions to access this nested exception: nested_ptr() and rethrow_nested(). If no exception is currently being handled by a catch block, the nested exception is a null exception_ptr. Later this object is thrown as an exception that combines both the currently handled exception and e. |
void rethrow_if_nested (const T& e) | Throws the exception nested in e, if (and only if) e is publicly and unambiguously derived from nested_exception. Otherwise, the function does nothing (and does not throw). If the nested exception is null, the function calls terminate instead. |
Alternatively, it can append additional exception on top of current exception and rethrow it in the catch block by calling throw_with_nested().
For example, after the database error is caught in the catch block, the database layer can do one of the following:
Scenario 1
- Create a nested_exception object explicitly, embedding current exception in it by calling its constructor.
- Call rethrow_nested () on the nested_exception object to throw the embedded exception into a catch block. Note that by calling nested_ptr() on the nested_exception object, an exception_ptr can be generated containing nested exception.
This example 5 below depicts this scenario.
void handle_ne(const nested_exception& ne) { try { if (ne.nested_ptr()) ne.rethrow_nested(); } catch(const exception& e) { cerr << "Caught exception: '" << e.what() << "'\n"; } } nested_exception ne; try { // this generates a out_of_range string().at(1); } catch(...) { // capture ne = nested_exception(); } /* prints Caught exception: 'basic_string::at: __n (which is 1) >= this->size() (which is 0)' */ handle_ne(ne);
Scenario 2
- Create a nested_exception object implicitly, embedding current exception and append an additional custom exception on top it, attach it in a separate exception object and throw it by calling throw_with_nested(). This will be caught by the next catch handler in the call chain, which can further add on its custom exception in the embedded nested_exception and propagate it.
- Process the current exception without rethrow.
- Repeatedly call rethrow_if_nested () to get exception details from the exception object in a catch block to get hierarchy of the embedded exceptions.
This example 6 below depicts this scenario.
/*prints exception: run() failed exception: Couldn't open nonexistent.file exception: basic_ios::clear: iostream error */ // prints the explanatory string of an exception. If the exception is nested, // recurses to print the explanatory of the exception it holds void print_exception(const exception& e, int level = 0) { cerr << string(level, ' ') << "exception: " << e.what() << endl; try { rethrow_if_nested(e); } catch(const exception& nestedException) { print_exception(nestedException, level+1); } catch(...) {} } // sample function that catches an exception and wraps it in a nested exception void open_file(const string& s) { try { ifstream file(s); file.exceptions(ios_base::failbit); } catch(...) { throw_with_nested( runtime_error("Couldn't open " + s) ); } } // sample function that catches an exception and wraps it in a nested exception void run() { try { open_file("nonexistent.file"); } catch(...) { throw_with_nested( runtime_error("run() failed") ); } } try { run(); } catch(const exception& e) { print_exception(e); }
No comments:
Post a Comment