Skip to content
C++

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); // fallback

Error 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

ScenarioMechanism
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-criticalstd::error_code
Programmer contract violationassert (not exceptions)

Exception safety guarantees

LevelMeaning
No-throwNever throws (noexcept)
StrongIf throws, state unchanged (commit-or-rollback)
BasicIf throws, no leaks, invariants hold; state may change
NoneMay 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
}
Edit on GitHub