We have some basic problems when trying to define error management in C:
- There is no “standard” way of reporting errors. Each company / project / programmer has a different approach
- Given the basic approaches, you cannot guarantee the error will be acted upon.
- There are difficulties with error propagation; particularly with nested calls.
The C++ exception mechanism gives us a facility to deal with run-time errors or fault conditions that make further execution of a program meaningless.
In C++98 it is possible to specify in a function declaration which exceptions a function may throw.
The above function declarations state:
- get_value() can throw any exception. This is the default.
- display() will not throw any exceptions.
- set_value() can throw exceptions of only of type char* and Sensor_Failed; it cannot throw exceptions of any other type.
This looks wonderful, but compilers (can) only partially check exception specifications at compile-time for compliancy.
If process() throws an exception of any type other than std::out_of_range this will cause the exception handling mechanism – at run-time – to call the function std::unexpected() which, by default, calls std::terminate() (although its behaviour can – and probably should – be replaced).
Because of the limitations of compile-time checking, for C++11 the exception specification was simplified to two cases:
- A function may propagate any exception; as before, the default case
- A function may not throw any exceptions.
Marking a function as throwing no exceptions is done with the exception specifier, noexcept.
(If you read the noexcept documentation you’ll see it can take a boolean constant-expression parameter. This parameter allows (for example) template code to conditionally restrict the exception signature of a function based on the properties of its parameter type. noexcept on its own is equivalent to noexcept(true). The use of this mechanism is beyond the scope of this article.)
On the face of it, the following function specifications look semantically identical – both state that the function will not throw any exceptions:
The difference is in the run-time behaviour and its consequences for optimisation.
With the throw() specification, if the function (or one of its subordinates) throws an exception, the exception handling mechanism must unwind the stack looking for a ‘propagation barrier’ – a (set of) catch clauses. Here, the exception specification is checked and, if the exception being thrown doesn’t match the provided specification, std::unexpected() is called.
However, std::unexpected() can itself throw an exception. If the exception thrown by std::enexpected() is valid for the current exception specification, exception propagation and stack unwinding continues as before.
This means that there is little opportunity for optimisation by the compiler for code using a throw() specification; in fact, the compiler may even introduce pessimisations to the code:
- The stack must be maintained in an unwindable state.
- Destructor order must be maintained to ensure objects going out of scope as a result of the exception are destroyed in the opposite order to their construction.
- The compiler may introduce new propagation barriers to the code, introducing new exception table entries, thus making the exception handling code bigger.
- Inlining may be disabled for the function.
In contrast, in the case of a noexcept function specification std::terminate() is called immediately, rather than std::unexpected(). Because of this, the compiler has the opportunity to not have to unwind the stack during an exception, allowing it a much wider range of optimisations.
In general, then, if you know your function will never throw an exception, prefer to specify it as noexcept, rather than throw().
Can’t wait? Download the full set of articles as a PDF, here.
To learn more about Feabhas’ Modern C++ training courses, click here.