C++ std::exception Hierarchy Explained
This article provides an overview of the standard exception hierarchy
in C++, which is anchored by the base class std::exception.
It examines the structural division between logic errors and runtime
errors, outlines the specialized exceptions generated by language
features and the standard library, and explains how to catch and extend
these classes effectively in modern C++ applications.
The Root: std::exception
In C++, all standard library exceptions derive from the base class
std::exception, defined in the
<exception> header. This class provides an interface
for error handling across the standard library. Its most critical member
function is:
virtual const char* what() const noexcept;This returns an implementation-defined, null-terminated explanatory
string describing the cause of the failure. Polymorphic exception
handling relies on catching const std::exception&,
allowing applications to intercept any standard-derived error
safely.
The Two Primary Branches: std::logic_error and std::runtime_error
Defined in the <stdexcept> header, the standard
hierarchy branches into two distinct categories representing preventable
programming defects versus unpredictable execution-state failures.
1. std::logic_error
std::logic_error represents faults in the internal logic
of a program that, in theory, could be avoided by writing better code or
enforcing proper preconditions.
The standard library derives several classes from
std::logic_error:
std::invalid_argument: Thrown when an unacceptable argument is passed to a function (e.g., passing invalid format strings tostd::stoi).std::domain_error: Thrown when an input argument lies outside the mathematical domain for which an operation is defined.std::length_error: Thrown when an attempt is made to exceed the maximum allowed size for an object (e.g., resizing astd::stringorstd::vectorbeyond itsmax_size()).std::out_of_range: Thrown when attempting to access elements outside the valid index range, such as withstd::vector::atorstd::string::at.std::future_error: Thrown by asynchronous operations in<future>when handling invalid promises or futures (derived fromstd::logic_errorsince C++11).
2. std::runtime_error
std::runtime_error represents events that cannot be
easily predicted by static code analysis and depend on external factors,
runtime state, or resource availability.
Common derivatives of std::runtime_error include:
std::range_error: Thrown when an internal computation produces a result outside the valid range.std::overflow_error: Thrown when an arithmetic operation produces an overflow condition.std::underflow_error: Thrown when a floating-point computation produces an underflow condition.std::system_error: Introduced in C++11 to report operating system and low-level failures; it contains anstd::error_code. Key derived classes include:std::ios_base::failure: Thrown by stream I/O operations (derived fromstd::system_errorsince C++11).std::filesystem::filesystem_error: Thrown by the filesystem library in C++17 on path or I/O failure.
Language and Resource Management Exceptions
Certain language expressions and core library components derive
directly from std::exception rather than passing through
std::logic_error or std::runtime_error.
Memory and Allocation
std::bad_alloc(<new>): Thrown by dynamic memory allocation operators (new) when system memory is exhausted.std::bad_array_new_length: Derived fromstd::bad_alloc, thrown when allocating an array with a negative or excessive size.
Type Safety and RTTI
std::bad_cast(<typeinfo>): Thrown bydynamic_castwhen a cast to a reference type fails.std::bad_typeid(<typeinfo>): Thrown when applying thetypeidoperator to a null pointer of a polymorphic type.std::bad_any_cast(<any>): Thrown bystd::any_castwhen converting to an incompatible target type.
Modern C++ Functional and Vocabulary Types
std::bad_function_call(<functional>): Thrown when invoking an emptystd::functionwrapper.std::bad_weak_ptr(<memory>): Thrown when constructing astd::shared_ptrfrom an expiredstd::weak_ptr.std::bad_optional_access(<optional>): Thrown when accessing the value of an emptystd::optionalvia.value().std::bad_variant_access(<variant>): Thrown when accessing the wrong alternative within anstd::variantviastd::get.
Execution Flow
std::bad_exception(<exception>): Used historically with dynamic exception specifications, and currently with exception handling mechanisms involving unexpected types.
Hierarchical Catching and Inheritance
Because the exception hierarchy relies on inheritance, catch blocks should always be arranged from the most derived type to the most base type to prevent base-class handlers from intercepting derived errors prematurely:
try {
// Operations that may throw
} catch (const std::out_of_range& e) {
// Handles specific boundary issues
} catch (const std::logic_error& e) {
// Handles other logic defects
} catch (const std::runtime_error& e) {
// Handles runtime execution issues
} catch (const std::exception& e) {
// Fallback for all other standard exceptions
}When authoring custom exceptions, derive from
std::runtime_error or std::logic_error instead
of std::exception directly, as their constructors natively
accept custom string messages to initialize what().