C++ Error Handling and Exceptions
Separate expected alternatives from exceptional failures using return values, optional results, exceptions, and RAII-based cleanup.
Before this lesson
Distinguish expected outcomes from exceptional failure
Use std optional for an optional value
Preserve invariants and cleanup when exceptions occur
The short answer
Choose an error channel from the contract: bool or optional for expected absence, a result-like value when callers need error details, and exceptions when a local caller cannot reasonably continue without handling failure.
Failure belongs in the interface
If an operation can fail, callers need a visible way to respond. Printing an error inside a reusable calculation hides the outcome. Returning an unchecked magic value also hides it. The function signature should distinguish success from failure and state what remains valid.
Expected alternatives include “not found,” invalid user input, and a rejected withdrawal. Exceptional failures include broken invariants or an unavailable resource when the current operation cannot proceed. Context matters more than a universal list.
| Situation | Possible channel | Caller action |
|---|---|---|
| Search may find nothing | std::optional<T> | Check presence |
| Mutation may be rejected | bool | Keep prior valid state |
| Need error details | Result-like struct | Inspect value or error |
| Cannot fulfill contract locally | Exception | Handle at a meaningful boundary |
Optional represents value or absence
std::optional<T> contains either a T or no value. It fits parsing, lookup, and calculations where absence is expected and no extra error details are required. Check it in a Boolean context before using *result or result.value().
Optional is not a replacement for every pointer. It owns a possible value, while a pointer can refer to an existing object. Choose from ownership and semantics.
#include <iostream>
#include <optional>
std::optional<int> safe_divide(int numerator, int denominator)
{
if (denominator == 0) return std::nullopt;
return numerator / denominator;
}
int main()
{
if (auto result = safe_divide(12, 3))
std::cout << *result << '\n';
}Exceptions separate the exceptional path
Throw a standard or domain exception when a function cannot meet its contract and ordinary return flow would obscure the failure. Catch by const reference at a layer that can recover, translate the error, add useful context, or decide how the application exits.
Do not use exceptions for routine loop control. Do not catch and ignore unknown failures. Exception messages are diagnostic context, not a stable machine-readable error protocol.
RAII provides basic exception safety
When an exception unwinds the stack, destructors run for fully constructed local objects. Values, vectors, strings, streams, locks, and smart pointers release their resources. Manual raw ownership makes this guarantee harder to maintain.
Preserve invariants by calculating new state before committing it. Stronger operations either succeed completely or leave the original state unchanged. At minimum, an object must remain valid and resources must not leak.
Quick knowledge check
Answer before you reveal.
01Why not return -1 for every parse failure?
A sentinel can collide with valid domains and carries no checked distinction between a value and absence.
02Does catch(...) make a program reliable?
No. Catch only where the program can recover, translate, add context, or terminate deliberately; hiding failure can corrupt later behavior.
Exercise
Practice challenge
Write parse_positive_int that returns std::optional<int>. Test valid input, zero, negative input, extra characters, and non-numeric text.
Requirements
- Failure cannot be confused with a valid integer
- All rejected cases return std::nullopt
- The caller checks before dereferencing the result
Optional extension: Return a result struct containing either the value or a useful error message.
Open in C++ compilerLesson checkpoint
One small step locks it in
Mark this lesson complete, then keep the momentum going.
Clear up the details
Frequently asked questions
Are exceptions always slow?
Implementations optimize the non-throwing path differently, but throwing has cost. Choose from semantics first and measure relevant workloads.
What is exception safety?
It describes guarantees about state and resources when an operation throws, such as no leaks and preserved invariants.
When is bool enough?
When callers need only success or failure and the output or state contract remains unambiguous.