Exceptions
Stack unwinding, noexcept, constructors, destructors, and exception-safety guarantees.
25 questions
JuniorTheoryVery commonWhat is stack unwinding and how does RAII interact with exceptions?
What is stack unwinding and how does RAII interact with exceptions?
When an exception leaves a scope, C++ destroys fully constructed automatic objects in reverse order until a matching catch is found. RAII relies on this: destructors release files, locks, memory, and other resources during unwinding.
Common mistakes
- ✗Managing resources manually in code that can throw
- ✗Catching by value and slicing exception objects
- ✗Forgetting that partially constructed objects do not have their destructor called
Follow-up questions
- →What happens if a constructor throws?
- →Why should destructors generally be noexcept?
JuniorTheoryVery commonWhy is the rule "throw by value, catch by const reference"?
Why is the rule "throw by value, catch by const reference"?
Throw copies (or moves) the object into a runtime area, so throwing a pointer to a local dangles and throwing a base by value slices. Catching by reference avoids slicing and a copy; const shows the handler doesn't mutate it.
Common mistakes
- ✗Throwing a pointer to a stack object — dangling reference in the handler
- ✗Catching by value and slicing the dynamic type to the static catch type
- ✗Allocating the exception with
newand throwing the pointer — leaks unless the handler deletes
Follow-up questions
- →What does the runtime actually do to copy the thrown object?
- →Can you
throwanoexceptmove constructor type and have it move?
JuniorTheoryVery commonHow do try/throw/catch blocks work? Catch logic and order.
How do try/throw/catch blocks work? Catch logic and order.
throw expr unwinds the stack, calling destructors (RAII) until a matching catch is found. Thrown type must equal or publicly derive from caught. Clauses are tried in declaration order — derived before bases. catch(...) matches anything.
Common mistakes
- ✗Catching by value instead of
const&— slices the exception if the type is polymorphic - ✗Placing
catch(std::exception&)beforecatch(std::runtime_error&)— the more general handler shadows the specific one - ✗Throwing from a destructor while another exception is already propagating — calls
std::terminate
Follow-up questions
- →What is the difference between
throw;andthrow e;inside a catch block? - →What does the exception specification
noexceptactually guarantee, and what happens when violated?
MiddleDebuggingVery commonWhat happens if a destructor throws during stack unwinding?
What happens if a destructor throws during stack unwinding?
If a destructor lets an exception escape while another exception is already being unwound, the program calls std::terminate().
Open full question →Common mistakes
- ✗Marking a destructor noexcept(false) and relying on callers to catch it
- ✗Throwing from cleanup code while another exception is active
- ✗Putting important error reporting only into a destructor
Follow-up questions
- →What does std::uncaught_exceptions() report?
- →How do standard library containers use noexcept move constructors?
MiddleTheoryVery commonWhat's the difference between basic, strong, and nothrow exception guarantees?
What's the difference between basic, strong, and nothrow exception guarantees?
Basic: invariants hold, no leaks, valid state. Strong: complete or roll back, like a transaction. Nothrow: never throws (noexcept). vector::push_back is strong for copyable, basic for move-only. Strong is built via copy-and-swap.
Common mistakes
- ✗Claiming strong guarantee while the implementation can leave half-modified state on failure
- ✗Marking a function
noexceptand then having it call something that can throw - ✗Forgetting that copy-and-swap requires a noexcept swap
Follow-up questions
- →How does
vector::push_backdecide between move and copy on reallocation? - →Why is
noexceptmove important for the strong guarantee in containers?
JuniorTheoryCommonWhat does catch (...) do, and what are its limits?
What does catch (...) do, and what are its limits?
catch (...) is the ellipsis handler that catches any exception type, so it must be the last clause. Its limit: you get no named object, so you cannot inspect the exception directly — only throw; to re-raise it or std::current_exception() to capture it.
Common mistakes
- ✗Placing
catch (...)before a typed handler — it shadows every clause after it, making them dead code - ✗Using
catch (...)to silently swallow all errors instead of logging or re-throwing withthrow; - ✗Expecting
catch (...)to give access to the exception object the way a typed handler does
Follow-up questions
- →How do you re-raise the exception caught by a
catch (...)block? - →When is a top-level
catch (...)inmaina reasonable safety net?
JuniorTheoryCommonCan you throw an exception from a constructor? What gets destroyed?
Can you throw an exception from a constructor? What gets destroyed?
Yes. The object's destructor is NOT called (it was never fully constructed), but already-constructed members and bases are destroyed in reverse order and memory is freed. RAII members clean up reliably even on a partial-construction failure.
Common mistakes
- ✗Allocating raw memory with
newin a constructor body before another potentially-throwing operation — the allocation leaks if the later throw bypasses thedelete - ✗Expecting the destructor to clean up after a constructor throw — it won't run; use RAII members instead
- ✗Throwing from a constructor of a class with a
noexceptconstructor — callsstd::terminate
Follow-up questions
- →How do you write an exception-safe constructor that acquires two resources?
- →What is a 'function try block' on a constructor and when would you use it?
JuniorTheoryCommonWhat is the standard exception hierarchy in C++ and when do you derive from it?
What is the standard exception hierarchy in C++ and when do you derive from it?
Root std::exception with virtual const char* what(). Branches: std::logic_error (precondition bugs), std::runtime_error (runtime conditions). Plus std::bad_alloc, std::bad_cast. Derive from the closest base so generic catch works.
Common mistakes
- ✗Throwing raw strings or ints — no
what(), no slicing-safe catch - ✗Deriving from
std::exceptionwithout overridingwhat()— useless message - ✗Confusing
logic_error(a bug) withruntime_error(a condition outside program control)
Follow-up questions
- →Why is
std::system_errorimportant for cross-platform error reporting? - →What does
std::nested_exceptionadd?
JuniorDesignCommonYou are reviewing error-handling code and must decide, for each failing condition, whether it should be guarded by an assert or signalled by throwing an exception. Explain how you draw that line and what it means for release builds.
You are reviewing error-handling code and must decide, for each failing condition, whether it should be guarded by an assert or signalled by throwing an exception. Explain how you draw that line and what it means for release builds.
assert checks programmer invariants — bugs that shouldn't happen; compiles out in release. Exceptions handle runtime conditions outside the program: I/O, parse errors, OOM. Rule: assert = fix the code; exception = world isn't cooperating.
Common mistakes
- ✗Using
assertfor input validation — disappears in release builds - ✗Throwing for impossible internal states — better to assert and crash early
- ✗Using
assert(expr && ...)with side effects — those side effects also disappear in release
Follow-up questions
- →What does C++26
contractsproposal add? - →What is
__builtin_unreachable()and when to use it?
JuniorTheoryCommonWhen is std::terminate called, and can you customize its behaviour?
When is std::terminate called, and can you customize its behaviour?
std::terminate runs on unrecoverable situations: an exception escaping noexcept, a throw during stack unwinding, no matching handler, or a failed exception construction. It calls the current handler — by default std::abort. You replace it with std::set_terminate.
Common mistakes
- ✗Letting an exception escape a
noexceptfunction or a destructor and being surprised by an instant crash - ✗Expecting a terminate handler to be able to resume normal execution — it must end the program
- ✗Confusing
std::set_terminatewithstd::set_new_handler, which addresses a different failure
Follow-up questions
- →Why must a terminate handler not return to its caller?
- →What is the difference between
std::terminateandstd::abort?
MiddleDesignCommonYou are designing the error-reporting strategy for a library and weighing return-based error reporting (std::error_code or C++23 std::expected) against throwing exceptions. Explain which situations push you toward error codes rather than exceptions, and why.
You are designing the error-reporting strategy for a library and weighing return-based error reporting (std::error_code or C++23 std::expected) against throwing exceptions. Explain which situations push you toward error codes rather than exceptions, and why.
Exceptions: truly exceptional conditions across many frames. Error codes / std::expected (C++23): hot paths, loop-called APIs, -fno-exceptions builds (embedded, kernel), or when failure is normal control flow.
Common mistakes
- ✗Throwing for parse failures in a hot loop and tanking performance
- ✗Returning bool plus an out-parameter for the result —
std::optionalorstd::expectedis clearer - ✗Catching everything as
std::exceptionand losing the specific error code information
Follow-up questions
- →What does
std::expected<T, E>add overstd::variant<T, E>? - →How does
std::error_categoryenable extensible error codes?
MiddleTheoryCommonWhat are the exception safety guarantees? Which do STL containers provide?
What are the exception safety guarantees? Which do STL containers provide?
Four levels: no guarantee, basic (no leaks, valid state), strong (commit-or-rollback), no-throw (noexcept). STL containers give strong for single-element ops like push_back, basic for multi-element ones.
Common mistakes
- ✗Thinking RAII alone provides the strong guarantee — RAII prevents leaks (basic), but rollback requires copy-and-swap or similar
- ✗Not marking move constructors
noexcept—std::vectorfalls back to copying on reallocation, losing the performance benefit of move - ✗Assuming all STL operations are strongly exception-safe — e.g.,
std::vector::insertin the middle provides only the basic guarantee
Follow-up questions
- →How does the copy-and-swap idiom implement the strong exception guarantee?
- →What guarantee does
std::map::operator[]provide when inserting a new key?
MiddleTheoryCommonWhat happens if an exception escapes a noexcept function?
What happens if an exception escapes a noexcept function?
If an exception escapes a noexcept function, std::terminate runs immediately — stack may not unwind, destructors may not run, not catchable. The intent: let compilers optimise (especially moves) by skipping exception tables.
Common mistakes
- ✗Marking a destructor
noexcept(false)— destructors are implicitlynoexceptsince C++11; explicitly opting out is almost always wrong - ✗Calling a potentially-throwing function inside a
noexceptfunction without try/catch — leads toterminateon exceptions - ✗Thinking
noexceptis a runtime guarantee — it's a contract; violation causes terminate, not a catchable exception
Follow-up questions
- →Why does
std::vectorprefer the move constructor only if it isnoexcept? - →What is
noexcept(noexcept(expr))and when would you write it?
MiddleTheoryCommonWhat does a bare throw; in a catch block do?
What does a bare throw; in a catch block do?
Bare throw; re-raises the active exception without copying — the original dynamic type is preserved, key when re-throwing through a base-class catch. throw e; copies e and slices to the static type if e is a value.
Common mistakes
- ✗Writing
throw e;instead ofthrow;in a catch — copies and may slice the exception - ✗Using bare
throw;outside a catch context — callsstd::terminate - ✗Not knowing that
std::current_exceptioncaptures the active exception for later re-throwing withstd::rethrow_exception
Follow-up questions
- →When would you use
std::current_exceptionandstd::rethrow_exception? - →How do you add context to an exception (log a message) and then re-throw it?
JuniorTheoryOccasionalHow do you handle division by zero in C++?
How do you handle division by zero in C++?
Integer division by zero is UB — may crash or trigger SIGFPE, NOT catchable via try/catch. Validate first: if (b == 0) throw std::domain_error(...);. Floating-point follows IEEE 754: yields +inf, -inf, or NaN, no signal.
Common mistakes
- ✗Trying to catch integer division by zero with
catch(...)— the SIGFPE signal is not a C++ exception - ✗Not distinguishing between integer and floating-point division semantics
- ✗Using a try/catch to 'handle' floating-point NaN — NaN propagates silently; check with
std::isnan
Follow-up questions
- →How do you enable floating-point exceptions (FE_DIVBYZERO, FE_INVALID) to trap on bad operations?
- →What does
std::numeric_limits<double>::infinity()evaluate to and when is it produced?
MiddleTheoryOccasionalWhen does new throw std::bad_alloc and how do you handle out-of-memory in C++?
When does new throw std::bad_alloc and how do you handle out-of-memory in C++?
Default new throws std::bad_alloc on failure; new(std::nothrow) T returns nullptr. On Linux with overcommit, OOM-killer fires later, so catching is unreliable. Strategies: preallocate, install set_new_handler, or use non-throwing allocators.
Common mistakes
- ✗Catching
bad_allocand trying to allocate again in the handler — likely fails too - ✗Allocating large buffers in the handler — same OOM
- ✗Trusting
bad_allocon Linux withoutvm.overcommit_memory=2
Follow-up questions
- →How does
std::set_new_handlerwork? - →Why is overcommit a problem for OOM detection?
MiddleTheoryOccasionalWhat is std::expected, and how does it compare to throwing an exception?
What is std::expected, and how does it compare to throwing an exception?
std::expected<T, E> (C++23) holds either a value or an error E inline, with no allocation and no unwinding. Failure is an ordinary return checked via has_value(). Unlike exceptions, the error path costs the same as success and is visible in the signature.
Common mistakes
- ✗Calling
value()on anexpectedwithout checkinghas_value()first — throwsbad_expected_access - ✗Using
expectedfor truly exceptional failures that should abort, where an exception propagates more cleanly - ✗Ignoring the returned
expectedentirely so an error silently disappears
Follow-up questions
- →How do
and_thenandtransformchainstd::expectedresults? - →What does
std::expectedadd over returningstd::optional<T>?
MiddleTheoryOccasionalHow does the compiler match a thrown exception against multiple catch clauses?
How does the compiler match a thrown exception against multiple catch clauses?
Catch handlers are tried in source order, not best match — first type that accepts the throw wins. More-derived must come before bases, else the base catches and the derived is dead code. catch(...) is always last.
Common mistakes
- ✗Putting
catch(const std::exception&)beforecatch(const std::runtime_error&)— runtime_error never reached - ✗Using
catch(...)to silence all errors — hides bugs - ✗Forgetting that pointer hierarchies follow the same order rules
Follow-up questions
- →Why does the standard mandate source-order matching instead of most-derived?
- →How do you re-throw the exception caught by
catch(...)?
MiddleTheoryOccasionalHow did exception specifications evolve from throw() to noexcept?
How did exception specifications evolve from throw() to noexcept?
Old dynamic specs like throw(int) were checked at runtime and called std::unexpected on violation — costly and rarely useful. C++11 added noexcept (a no-throw contract), deprecated dynamic specs, made throw() equivalent to noexcept, and C++17 removed them entirely.
Common mistakes
- ✗Writing
throw(SomeType)in modern code — removed in C++17, will not compile - ✗Assuming
throw()andnoexceptdiffer in violation behaviour — both end up callingstd::terminate - ✗Believing dynamic specs were ever checked at compile time rather than at runtime
Follow-up questions
- →Why did the committee judge dynamic exception specifications a design failure?
- →How does
noexceptenable the move-or-copy optimisation instd::vector?
SeniorPerformanceOccasionalWhat is the runtime cost of exceptions when none are thrown vs when they are?
What is the runtime cost of exceptions when none are thrown vs when they are?
On Itanium ABI (Linux/macOS) the no-throw path is essentially free. Throwing is costly: DWARF walk, destructor calls, object allocation. Windows SEH has a small per-function setup. Throws run ~10-1000× slower than error codes.
Common mistakes
- ✗Throwing in a tight loop and being surprised by the throughput drop
- ✗Disabling exceptions globally to save the no-throw cost when there isn't any
- ✗Comparing throw cost to a single function call instead of equivalent error-code propagation
Follow-up questions
- →What is
-fno-exceptionsand when does it make sense? - →How does Itanium zero-cost work in detail (personality routine, LSDA)?
MiddleDebuggingRareCan you catch a stack overflow with a C++ try/catch?
Can you catch a stack overflow with a C++ try/catch?
No. Stack overflow is an OS signal (SIGSEGV on Linux, access violation on Windows), not a C++ exception. The runtime cannot unwind without stack space. Windows SEH can intercept the page fault, but that is not portable C++.
Common mistakes
- ✗Putting
try { recurse(); } catch (...) {}and expecting it to handle stack overflow - ✗Allocating large arrays on the stack in deeply nested calls
- ✗Mistaking stack overflow for
std::bad_alloc— different mechanism
Follow-up questions
- →How does Windows SEH differ from C++ exceptions?
- →What's a guard page and how does the OS detect overflow?
SeniorTheoryRareWhat is std::exception_ptr and how do you transport an exception between threads?
What is std::exception_ptr and how do you transport an exception between threads?
std::exception_ptr is a shared, type-erased smart pointer to a copy of an exception. In catch, current_exception() returns one; rethrow_exception(p) re-raises it elsewhere. std::promise::set_exception delivers worker errors to a future.
Common mistakes
- ✗Storing
std::exception_ptrand trying to inspect the exception withoutrethrow_exception+ catch - ✗Forgetting to handle the case where the ptr is null (no current exception)
- ✗Calling
current_exception()outside a handler and expecting non-null
Follow-up questions
- →What's the cost of
current_exception()(allocation, copy)? - →How does
std::futurepropagate worker exceptions?
SeniorTheoryRareWhat is a function-try-block and what is it specifically useful for?
What is a function-try-block and what is it specifically useful for?
A function-try-block wraps the whole body of a function and, for constructors, also the member-initialiser list. Its unique value is catching throws during member init. The handler can clean up but cannot suppress — runtime rethrows.
Common mistakes
- ✗Trying to swallow the exception in a constructor's function-try-block — runtime rethrows anyway
- ✗Using function-try-block on a free function as if it changes anything — it doesn't
- ✗Accessing
this's members in the constructor's catch — they may be partially constructed
Follow-up questions
- →Why can't a constructor's catch suppress the exception?
- →How does function-try-block interact with delegating constructors?
SeniorTheoryRareWhat is std::nested_exception and how do you use throw_with_nested / rethrow_if_nested?
What is std::nested_exception and how do you use throw_with_nested / rethrow_if_nested?
std::nested_exception attaches a captured prior exception to a new one to preserve a cause chain. throw_with_nested(e) throws e mixed with nested_exception plus the active exception. rethrow_if_nested(p) re-raises the cause.
Common mistakes
- ✗Forgetting
dynamic_casttonested_exceptionwhen manually unwinding the chain - ✗Calling
throw_with_nestedoutside an active handler — there's no current exception to capture - ✗Logging only the outer
what()— missing the chain
Follow-up questions
- →How does
std::current_exceptionrepresent an exception (std::exception_ptr)? - →When would you transport an
exception_ptracross threads?
SeniorTheoryRareWhat does std::uncaught_exceptions() return and what is it used for?
What does std::uncaught_exceptions() return and what is it used for?
std::uncaught_exceptions() (C++17) returns the count of in-flight uncaught exceptions on this thread. Used in destructors to detect unwinding — ScopeGuard rolls back if the count is higher than at construction.
Common mistakes
- ✗Using
uncaught_exception()(singular, deprecated) — broken when nested exceptions are involved - ✗Calling it outside a destructor and trying to make decisions based on it — fragile
- ✗Throwing from a destructor based on its return — usually still terminates
Follow-up questions
- →How does Andrei Alexandrescu's ScopeGuard use this?
- →Why was
uncaught_exception()replaced with the plural form?