Recoverable Runtime Errors

Learn about handling recoverable errors at runtime, using exceptions, and preserving the valid state of programs.

Handling recoverable errors at runtime

If a function cannot uphold its part of the contract (the postcondition, that is), a runtime error has occurred and needs to be signaled to someplace in the code that can handle it and recover the valid state.

The purpose of handling recoverable errors is to pass an error from the place where the error occurred to the place where the valid state can be recovered. There are many ways to achieve this. There are two sides to this coin:

  • For the signaling part, we can choose between C++ exceptions, error codes, returning a std::optional or std::pair, or using boost::outcome or std::experimental::expected.

  • Preserving the valid state of the program without leaking any resources. Deterministic destructors and automatic storage duration are the tools that make this possible in C++.

We will now focus on C++ exceptions and how to avoid leaking resources when recovering from an error.

Exceptions

Exceptions are the standard error handling mechanism provided by C++. The language was designed to be used with exceptions. One example of this is constructors that fail; the only way to signal errors from constructors is by using exceptions.

Exceptions can be used in many different ways. One reason for this is that distinct applications can have vastly different requirements when dealing with runtime errors. With some applications, such as a pacemaker or a power plant control system, which may have a severe impact if they crash, we may have to deal with every possible exceptional circumstance, such as running out of memory and keeping the application in a running state.

Some applications even completely stay away from using the heap memory, either because the platform doesn't have any heap available at all, or because the heap introduces an uncontrollable level of uncertainty as the mechanics of allocating new memory are out of the application's control.

We already know the syntax of throwing and catching exceptions and will not cover it here. A function that is guaranteed to not throw an exception can be marked as noexcept. It's important to understand that the compiler does not verify this; instead, it is up to the author of the code to figure out whether their function could throw an exception.

A function marked with noexcept makes it possible for the compiler to generate faster code in some cases. If an exception would be thrown from a function marked with noexcept, the program will call std::terminate() instead of unwinding the stack. The following code demonstrates how to mark a function as not throwing:

Get hands-on with 1200+ tech skills courses.