An exception transfers control and information to a handler, from the point where an error occurs, across any number of function calls [except].
Syntax
try-block:
try compound-statement handler-seq
function-try-block:
try [ctor-initializer] compound-statement handler-seq
handler:
catch ( exception-declaration ) compound-statement
exception-declaration:
[attribute-specifier-seq] type-specifier-seq declarator
[attribute-specifier-seq] type-specifier-seq [abstract-declarator]
...
throw-expression:
throw [assignment-expression]
void load(const std::string& path)
{
std::ifstream in(path);
if (!in)
throw std::runtime_error("cannot open " + path);
}
try {
load("data.txt");
} catch (const std::runtime_error& e) {
std::cerr << e.what() << '\n';
} catch (...) {
std::cerr << "unknown error\n";
throw; // rethrows the same exception object
}
throw
throw e copy-initialises an exception object from e
[except.throw]. The object has an unspecified location and lasts until the last
handler that deals with it exits. The operand’s static type gives the object’s type, so
throwing through a reference to a base slices. Any complete object type can be thrown, or a
pointer, but standard practice is to throw a class derived from
std::exception.
throw; with no operand rethrows the exception currently being handled. If
there is none, std::terminate is called.
Handlers
Handlers are tried in order, and the first match wins [except.handle]. A handler of type
T, const T, T& or const T& matches an
exception of type E if:
TandEare the same type, ignoring top-level cv-qualifiers.Tis an unambiguous public base class ofE.Tis a pointer type, andEcan convert to it by a standard pointer conversion, a qualification conversion, or a function pointer conversion.Tis a pointer or pointer-to-member andEisstd::nullptr_t.
No arithmetic or user-defined conversion is applied. Catch by reference to avoid slicing,
and put derived types before their bases. catch (...) matches anything,
and must be last.
Stack unwinding
When an exception is thrown, control passes to the nearest matching handler. On the way,
each automatic object constructed since the try block was entered is destroyed, in
reverse order [except.ctor]. This is stack unwinding, and it is what makes resource
management with destructors work. If no handler exists, whether the stack is unwound
before termination is implementation-defined.
std::uncaught_exceptions() (C++17) returns the number of uncaught exceptions in
the current thread. It can distinguish a destructor run normally from one run during
unwinding.
Constructors and destructors
- If a constructor throws, the object’s fully constructed members and bases are destroyed, and its destructor is not run [except.ctor]. For a
new-expression, the storage is released. - Destructors are implicitly
noexceptsince C++11. An exception that escapes one callsstd::terminate. - A destructor that throws during stack unwinding always calls
std::terminate.
Function try blocks
A function try block covers the whole function body and, in a constructor, its member initialisers [except.pre].
struct Holder {
Resource r;
Holder(int id) try : r(id) {
/* body */
} catch (const std::exception& e) {
log(e.what());
// the exception is implicitly rethrown at the end of this handler
}
};
In a constructor or destructor, the current exception is rethrown when control reaches the end of the handler. There, referring to non-static members is undefined behaviour, since the object’s lifetime is over [except.handle].
noexcept
| Form | Meaning | Since |
|---|---|---|
void f() noexcept; | Promises not to throw | C++11 |
void f() noexcept(cond); | Promises not to throw, if the condition is true | C++11 |
noexcept(expr) | Operator: true if the expression cannot throw | C++11 |
throw() | Old equivalent of noexcept; deprecated, removed in C++20 | C++98 |
throw(T, U) | Dynamic exception specification; removed in C++17 | C++98 |
If an exception would leave a noexcept function, std::terminate is called
[except.spec]. Since C++17 the specification is part of the
function type, and a
noexcept function pointer converts to one that may throw, but not the reverse.
Moves should be noexcept: std::vector copies instead of moving
elements on reallocation if a move might throw.
std::terminate
std::terminate is called, for example, in these cases [except.terminate]:
- No matching handler exists for a thrown exception.
- An exception escapes a
noexceptfunction. - A destructor exits with an exception during stack unwinding.
throw;is used with no current exception.- A joinable
std::threadis destroyed.
By default it calls std::abort. std::set_terminate installs a handler.
Standard exception classes
All derive from std::exception in <exception>, and
what() returns a message.
| Class | Thrown for | Header |
|---|---|---|
std::logic_error | Errors in program logic | <stdexcept> |
std::invalid_argument, std::domain_error, std::length_error, std::out_of_range | Derived from logic_error | <stdexcept> |
std::runtime_error | Errors detected at run time | <stdexcept> |
std::range_error, std::overflow_error, std::underflow_error | Derived from runtime_error | <stdexcept> |
std::system_error | Operating system errors, derived from runtime_error (C++11) | <system_error> |
std::bad_alloc | Allocation failure | <new> |
std::bad_cast | A failed reference dynamic_cast | <typeinfo> |
std::bad_typeid | typeid on a null pointer dereference | <typeinfo> |
std::bad_function_call | Calling an empty std::function (C++11) | <functional> |
std::bad_optional_access | Reading an empty std::optional (C++17) | <optional> |
std::bad_variant_access | Reading the wrong alternative of a variant (C++17) | <variant> |
std::bad_any_cast | Casting std::any to the wrong type (C++17) | <any> |
std::bad_expected_access | Reading the value of an std::expected with an error (C++23) | <expected> |
std::exception_ptr and std::current_exception (C++11) carry an
exception between threads. std::nested_exception (C++11) keeps a chain of causes.
Both are in <exception>.