C++ Cheatsheet

Exceptions

Use this C++ reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

Throw and Catch

#include <stdexcept>
#include <exception>

// Throw any copyable type
throw 42;                           // throw int (unusual)
throw std::runtime_error("oops");   // throw an exception object (preferred)
throw;                              // rethrow current exception (in catch block only)

// Catch block: matches by type (most-derived first)
try {
    doSomethingRisky();
} catch (const std::invalid_argument& e) {   // specific type first
    std::cerr << "invalid arg: " << e.what() << "\n";
} catch (const std::runtime_error& e) {
    std::cerr << "runtime error: " << e.what() << "\n";
} catch (const std::exception& e) {          // base class
    std::cerr << "exception: " << e.what() << "\n";
} catch (...) {                              // catch-all (last resort)
    std::cerr << "unknown exception\n";
    throw;   // rethrow to preserve original exception
}

Standard Exception Hierarchy (<stdexcept>)

std::exception
├── std::bad_alloc           (new fails; <new>)
├── std::bad_cast            (dynamic_cast fails; <typeinfo>)
├── std::bad_typeid          (<typeinfo>)
├── std::bad_function_call   (calling empty std::function)
├── std::bad_optional_access (accessing empty optional; C++17)
├── std::bad_variant_access  (get on wrong type; C++17)
├── std::logic_error
│   ├── std::invalid_argument
│   ├── std::domain_error
│   ├── std::length_error
│   ├── std::out_of_range
│   └── std::future_error    (<future>)
└── std::runtime_error
    ├── std::range_error
    ├── std::overflow_error
    ├── std::underflow_error
    └── std::system_error    (<system_error>)
#include <stdexcept>

throw std::invalid_argument("parameter must be positive");
throw std::out_of_range("index " + std::to_string(i) + " out of bounds");
throw std::runtime_error("failed to open file");
throw std::length_error("container too large");
throw std::overflow_error("arithmetic overflow");

// std::system_error (OS errors)
#include <system_error>
throw std::system_error(errno, std::generic_category(), "read failed");

// Access what()
try { /* ... */ }
catch (const std::exception& e) { std::cerr << e.what(); }

Custom Exception Classes

#include <stdexcept>
#include <string>

// Minimal custom exception
class DatabaseError : public std::runtime_error {
public:
    explicit DatabaseError(const std::string& msg)
        : std::runtime_error(msg) {}
};

// With extra data
class HttpError : public std::runtime_error {
    int code_;
public:
    HttpError(int code, const std::string& msg)
        : std::runtime_error(msg), code_(code) {}
    int code() const noexcept { return code_; }
};

// Usage
try {
    throw HttpError(404, "Not Found");
} catch (const HttpError& e) {
    std::cerr << e.code() << " " << e.what() << "\n";
} catch (const std::exception& e) {
    std::cerr << e.what() << "\n";
}

noexcept

// Declare that a function will never throw
// If it does, std::terminate() is called (no unwinding)
int safe() noexcept { return 42; }

// Conditional noexcept
template<typename T>
void swap(T& a, T& b) noexcept(std::is_nothrow_swappable_v<T>) {
    using std::swap;
    swap(a, b);
}

// Query at compile time
constexpr bool n1 = noexcept(safe());     // true
constexpr bool n2 = noexcept(risky());    // false if risky() is not noexcept

// Move operations should be noexcept for std::vector reallocation optimization
class MyClass {
public:
    MyClass(MyClass&&) noexcept = default;
    MyClass& operator=(MyClass&&) noexcept = default;
};

Exception Safety Guarantees

LevelGuarantee
No-throwNever throws; if it does, std::terminate (mark noexcept)
StrongEither succeeds completely or state is unchanged (commit-or-rollback)
BasicOn exception, program is in a valid (but unspecified) state; no leaks
NoneNo guarantee — avoid

Copy-and-swap idiom (strong guarantee for assignment):

class Buffer {
    int* data_;
    std::size_t size_;
public:
    // swap: noexcept exchange of internals
    friend void swap(Buffer& a, Buffer& b) noexcept {
        using std::swap;
        swap(a.data_, b.data_);
        swap(a.size_, b.size_);
    }

    // Copy assignment with strong guarantee:
    Buffer& operator=(Buffer other) {   // copy made in parameter (may throw there)
        swap(*this, other);             // noexcept exchange
        return *this;                   // old data destroyed in 'other' at scope end
    }
};

Stack Unwinding and RAII

When an exception is thrown, the stack is unwound: destructors of all local objects are called in reverse order of construction. This is why RAII-based resource management is exception-safe.

void process() {
    std::unique_ptr<Widget> w = std::make_unique<Widget>(); // RAII
    std::vector<int> v = {1, 2, 3};
    throw std::runtime_error("fail");
    // ↑ unique_ptr and vector destructors still run — no leak
}

// Destructors MUST NOT throw. If they do and the stack is already
// unwinding, std::terminate() is called.
class Safe {
public:
    ~Safe() noexcept {     // destructor is implicitly noexcept; make it explicit
        try { cleanup(); }
        catch (...) {}     // swallow all exceptions in destructor
    }
};

Nested Exceptions (C++11)

// Catch, add context, and rethrow with nested original exception
void inner() { throw std::runtime_error("inner problem"); }

void outer() {
    try { inner(); }
    catch (...) {
        std::throw_with_nested(std::runtime_error("outer context"));
    }
}

// Print full chain
void printNested(const std::exception& e, int depth = 0) {
    std::cerr << std::string(depth * 2, ' ') << e.what() << "\n";
    try {
        std::rethrow_if_nested(e);   // rethrows nested if present
    } catch (const std::exception& inner) {
        printNested(inner, depth + 1);
    } catch (...) {}
}

try { outer(); }
catch (const std::exception& e) { printNested(e); }

std::exception_ptr — Store and Transfer Exceptions

#include <exception>

std::exception_ptr eptr;

try {
    throw std::runtime_error("stored");
} catch (...) {
    eptr = std::current_exception();   // capture any active exception
}

// ... later / in another thread ...
if (eptr) {
    try {
        std::rethrow_exception(eptr);  // rethrow the stored exception
    } catch (const std::exception& e) {
        std::cerr << e.what() << "\n";
    }
}

// Used in std::promise/std::future to propagate exceptions across threads

std::terminate and Termination

// Termination is called when:
// - An exception is thrown and not caught
// - A noexcept function throws
// - An exception is thrown during stack unwinding (second active exception)
// - A pure virtual function is called
// - std::terminate() is called directly

// Install custom terminate handler
std::set_terminate([](){
    std::cerr << "Unhandled exception!\n";
    std::abort();
});

Best Practices

  • Throw by value, catch by const reference:
throw std::runtime_error("msg");                    // by value
catch (const std::runtime_error& e) { e.what(); }  // by const&
// Never catch by value: catch (std::runtime_error e) — copies and may slice
// Never catch by pointer: catch (std::runtime_error* e)
  • Catch most-derived types first — base class handlers shadow derived ones.
  • Destructors must not throw — mark them noexcept.
  • Move operations should be noexcept — required for strong guarantee in std::vector resize.
  • Use RAII — never new without a smart pointer; never acquire resources without a destructor to release them.
  • catch (...) at the top level — log and abort; never silently swallow exceptions.
  • if constexpr over exception for control flow — exceptions are for truly exceptional conditions, not normal program logic.