While exceptions are a common feature of modern languages, there are several reasons to potentially avoid them:
- They make the control flow of a program more difficult to understand because they introduce additional hidden exit points.
- It is difficult to introduce them gradually in a codebase that was not designed with exceptions in mind.
- They add to the size of each binary produced, thereby increasing both compile time and final executable size.
- They may incur a small performance penalty.
- The time required to handle an exception is not easy to assess, which makes them difficult to use for hard real-time applications.
If a project decides not to use exceptions, some other error-handling mechanisms must be used. One option is to immediately terminate the process
when unrecoverable errors are detected. Another one is to use the return value of the functions to convey error information and explicitly check for
this value at the call sites. This error information then has to be manually propagated up the call stack until reaching a point where recovery is
possible.
Starting with C++23, the standard library provides the std::expected
class that allows packing into a single object either the normal
return value of a function when the execution succeeded or some error information when it failed. This type also simplifies checking for errors at the
call site.
This rule raises an issue when:
- an exception is thrown
- a
try
-catch
block is used
- an exception specification (
throw(xxx)
) is present.
The rule applies both for C++ and Objective-C exception mechanisms.
Noncompliant code example
enum class MyFunctionErrors{NotALetter, NotUppercase};
double myfunction(char param) throw(MyFunctionErrors); // Noncompliant
void f() {
try // Noncompliant
{
doSomething();
throw std::runtime_error{"some error"}; // Noncompliant
}
catch (...)
{
// handle exception
}
}
Compliant solution
enum class MyFunctionErrors{NotALetter, NotUppercase};
std::expected<double, MyFunctionErrors> myfunction(char param); // Compliant
void functionThatShallNotFail() noexcept; // Compliant
bool f() {
if (!doSomething()); {
// Handle the situation
return false;
}
// Rest of the code
return true;
}
Exceptions
noexcept
specifications are ignored because even if you choose not to use exceptions in your code, it’s important to decorate as
noexcept
certain functions (for instance, move constructors that do not throw
, see S5018). This decoration can be
detected by type traits, and some meta-programming techniques rely on this information.