C++

Exception Handling

C has one universal error-handling convention: return a special value (or an error code) and hope every caller actually checks it. Exceptions are C++'s structured alternative — a function that hits a problem can throw, immediately unwinding up the call stack until something explicitly agrees to catch it, with every destructor along the way still running automatically.

Quick reference
What it is
A mechanism for signaling and handling errors by unwinding the call stack, rather than checking a return value at every call site.
Mental model
A fire alarm, not a note left on a desk — throw stops everything immediately and propagates upward until a catch block agrees to handle it, instead of relying on every single caller to remember to check.
Key numbers
An uncaught exception terminates the program immediately, via std::terminate — there is no silent fallthrough the way an unchecked C error code has.
Reach for it when
An error is genuinely exceptional (a file that should exist doesn't, a network call fails) and the calling code several layers up is what actually knows how to recover — not for routine, expected control flow.

try, catch, throw

#include <stdexcept>

float divide(float a, float b) {
    if (b == 0) {
        throw std::runtime_error("division by zero");   // throw — stop, unwind, look for a catch
    }
    return a / b;
}

int main() {
    try {
        float result = divide(10, 0);
        std::cout << result << std::endl;   // never reached
    } catch (const std::runtime_error &e) {
        std::cout << "Error: " << e.what() << std::endl;   // Error: division by zero
    }
    return 0;
}

divide never has to be called with a return-value check at every call site — the caller (here, several lines away in main) is where the actual recovery decision gets made, and every function in between needs no error-handling code of its own at all.

Stack unwinding and RAII

When an exception is thrown, C++ walks back up the call stack looking for a matching catch, running the destructor of every local object along the way — this is exactly why RAII matters so much in C++ specifically: a resource wrapped in an RAII object is released correctly even when an exception skips the rest of the function entirely.

void process() {
    FileHandle f("data.txt");   // RAII-managed — see Constructors, Destructors & RAII
    riskyOperation();             // throws
    // nothing after this line runs
}   // ~FileHandle() STILL runs during unwinding — the file is still closed correctly

What to throw

<stdexcept> provides a small hierarchy of standard exception types — throwing one of these (or your own class derived from std::exception) is standard practice over throwing a plain number or string:

std::runtime_error
A general error only detectable at runtime — the most commonly thrown type.
std::invalid_argument
A function argument that doesn't make sense (e.g. a negative size).
std::out_of_range
An index or key outside a valid range — thrown by std::vector::at(), for example.
std::logic_error
A violated precondition that, in principle, could have been checked before calling.

Exceptions vs. C-style error codes

C-style error codesExceptions
Easy to ignoreYes — an unchecked return value fails silentlyNo — an uncaught exception terminates the program loudly
Propagates automaticallyNo — every intermediate function must check and re-return itYes — unwinds automatically until caught
Runtime cost when not thrownNoneEffectively none on modern compilers
Runtime cost when thrownN/AReal, non-trivial — not meant for routine control flow

Real-world applications

Gotchas

Field notes