C++ has two main mechanisms to handle runtime errors:
- Exceptions
- Error codes
- extra: with
std::optional
nullable return type
- extra: with
Exceptions
The most common way of handling runtime errors is using exceptions - with try
, throw
and catch
statements.
Exceptions work well for desktop applications but have certain issues which make it undesirable for writing real-time code running on embedded hardware (e.g. medical devices or vehicle software).
Issues
- Breaks codeflow by potentially creating multiple exits
- Performance overhead - takes non-deterministic time for stack unwinding
- Can cause resource leaks, especially when calling legacy C++ code with raw pointers
- Requires heap memory allocation - which is usually a no-no for embedded standards compliance (e.g. AUTOSAR)
Many companies have a no exception policy.
Error codes
Returning error codes are a legacy from C. A function would return a sentinal value indicating an error and the caller is responsible for checking this.
Some common patterns:
bool func1() { return true; }
int func2() { return 2; }
enum class ErrorCode { SUCCESS = 1, INVALID_INPUT = 2}; // expressive + type-safe
ErrorCode func3() { return ErrorCode::SUCCESS; }
Issues
- At the call-site, a
if..else
chain is needed to handle errors - Return codes can be silently ignored
+ std::optional
🏗️
std::optional<T>
was introduced in C++17 and expresses nullability (“either a <T>
or nothing”). So the function can either (a) return null if there was an error or (b) the return value. However, we would lose information on the type of error.
Comments