Skip to content
C++

Exceptions — Quick Reference

C++ exceptions cheatsheet — throw/catch/rethrow, exception hierarchy, noexcept, RAII safety, exception_ptr, and when to use vs alternatives.

throw / catch / rethrow

cpp
try {
    throw std::runtime_error("something failed");
} catch (const std::invalid_argument& e) {
    // specific — checked first
} catch (const std::runtime_error& e) {
    std::println("runtime error: {}", e.what());
} catch (const std::exception& e) {
    // base class — catches all std exceptions
} catch (...) {
    // catch-all — rethrow or log only
    throw;  // rethrow preserves original type and stack
}

Standard Exception Hierarchy

cpp
std::exception
  std::logic_error
    std::invalid_argument
    std::domain_error
    std::length_error
    std::out_of_range
  std::runtime_error
    std::range_error
    std::overflow_error
    std::underflow_error
    std::system_error (has error_code)
  std::bad_alloc     (new failed)
  std::bad_cast      (dynamic_cast failed)
  std::bad_typeid    (typeid on null)
  std::bad_exception

Custom Exceptions

cpp
class AppError : public std::runtime_error {
    int code_;
public:
    AppError(int code, std::string msg)
        : std::runtime_error{std::move(msg)}, code_{code} {}
    int code() const { return code_; }
};

throw AppError{404, "resource not found"};

noexcept

cpp
void safe()  noexcept;           // never throws — optimizer hint
void risky() noexcept(false);    // may throw (default)

// noexcept operator — query at compile time
static_assert(noexcept(std::swap(a, b)));

// Conditional noexcept — forward noexcept from inner operation
template<typename T>
void wrap(T& x) noexcept(noexcept(x.process())) {
    x.process();
}

// IMPORTANT: if noexcept function throws anyway → std::terminate

RAII and Exception Safety

cpp
// RAII ensures cleanup even when exceptions are thrown
class ManagedFile {
    FILE* f_;
public:
    explicit ManagedFile(const char* path) : f_{fopen(path, "r")} {
        if (!f_) throw std::system_error(errno, std::generic_category());
    }
    ~ManagedFile() { if (f_) fclose(f_); }  // runs even on exception
};

void process() {
    ManagedFile f{"data.txt"};   // opened
    risky_operation();            // throws? f's destructor runs anyway
    // f closed automatically
}

exception_ptr — Transferring Exceptions

cpp
// Capture exception in current thread
std::exception_ptr eptr;
try {
    throw std::runtime_error("oops");
} catch (...) {
    eptr = std::current_exception();  // capture
}

// Rethrow in another context (e.g., from a thread)
if (eptr) {
    try {
        std::rethrow_exception(eptr);
    } catch (const std::exception& e) {
        std::println("caught from thread: {}", e.what());
    }
}

std::terminate and set_terminate

cpp
// Causes of std::terminate:
// - noexcept function that throws
// - exception thrown during stack unwinding (destructor throws)
// - uncaught exception reaching main()
// - std::thread exits with active exception

std::set_terminate([] {
    std::println(stderr, "terminate called — generating crash report");
    std::abort();
});

Exceptions vs Alternatives

cpp
Use exceptions when:
  ✅ Error is genuinely exceptional (rare, unexpected)
  ✅ Error must propagate up many call frames
  ✅ You need RAII cleanup on error paths

Use error codes / std::expected when:
  ✅ Error is a common/expected outcome (parse failure, file not found)
  ✅ Performance-critical hot path (exceptions have cost when thrown)
  ✅ Calling from C code or across ABI boundary
  ✅ Embedded systems that disable exceptions (-fno-exceptions)

Exception Safety Levels

cpp
noexcept     — never throws
strong       — if it throws, state is unchanged (rollback semantics)
basic        — if it throws, no leak, invariants held, state may change
none         — if it throws, anything can happen (avoid)
Edit on GitHub