C++ Error Handling Cheat Sheet
Quick reference for C++ error handling strategies — exceptions, std::expected, error codes, and when to use each.
Exceptions
cpp
#include <stdexcept>
// Throw
throw std::runtime_error("message");
throw std::invalid_argument("bad input");
throw std::out_of_range("index " + std::to_string(i));
// Custom exception
struct AppError : std::runtime_error {
int code;
AppError(int c, std::string msg)
: std::runtime_error(std::move(msg)), code(c) {}
};
// Catch — most derived first, always by const ref
try {
risky();
} catch (const AppError& e) {
handle(e.code, e.what());
} catch (const std::runtime_error& e) {
handle_runtime(e.what());
} catch (const std::exception& e) {
handle_any(e.what());
} catch (...) {
log_unknown(); throw; // don't swallow
}
// Rethrow (preserve type)
catch (const std::exception& e) { log(e.what()); throw; }
// NOT: throw e; (slices to base type)
// noexcept — terminate on throw, enables move optimizations
void swap(T& a, T& b) noexcept { }
Widget(Widget&&) noexcept = default;
// Store/transfer exceptions across threads
std::exception_ptr ep = std::current_exception();
// later: if (ep) std::rethrow_exception(ep);std::expected (C++23)
cpp
#include <expected>
// Return success or error without exceptions
std::expected<int, std::string> parse(std::string_view s) {
if (s.empty()) return std::unexpected("empty input");
return std::stoi(std::string(s));
}
// Check and access
auto r = parse("42");
if (r) use(*r); // has value
if (!r) log(r.error()); // has error
r.value(); // throws bad_expected_access if error
r.value_or(0); // fallback
// Chain
auto result = parse(input)
.and_then([](int n) -> std::expected<double, std::string> {
if (n < 0) return std::unexpected("negative");
return std::sqrt(n);
})
.transform([](double d) { return d * 2; })
.or_else([](auto) { return std::expected<double, std::string>{0.0}; });
// expected<void, E> for success/failure only
std::expected<void, Error> write() {
if (fail) return std::unexpected(Error::io);
return {};
}std::optional (no error info)
cpp
#include <optional>
std::optional<int> find(std::vector<int>& v, int val) {
auto it = std::ranges::find(v, val);
if (it == v.end()) return std::nullopt;
return *it;
}
auto r = find(v, 42);
if (r) use(*r);
r.value_or(-1); // fallbackError codes (C++11 system_error)
cpp
#include <system_error>
// Throw with error code
throw std::system_error(errno, std::generic_category(), "open failed");
// Catch
try { }
catch (const std::system_error& e) {
e.code().value(); // int error code
e.code().message(); // string description
e.what(); // "open failed: No such file or directory"
}
// Non-throwing — output parameter pattern
std::error_code ec;
fs::remove("file", ec);
if (ec) log(ec.message());When to use what
| Scenario | Mechanism |
|---|---|
| Unrecoverable error (logic bug) | assert / std::terminate |
| Rare exceptional failure (I/O, OOM) | throw / exceptions |
| Expected failure (parse, find) | std::expected or std::optional |
| C interop / performance-critical | std::error_code |
| Programmer contract violation | assert (not exceptions) |
Exception safety guarantees
| Level | Meaning |
|---|---|
| No-throw | Never throws (noexcept) |
| Strong | If throws, state unchanged (commit-or-rollback) |
| Basic | If throws, no leaks, invariants hold; state may change |
| None | May leak or break invariants (avoid) |
cpp
// Strong guarantee via copy-and-swap
void Container::assign(std::vector<T> new_data) { // copy or move
data_ = std::move(new_data); // noexcept — strong guarantee
}