Chaining C++ Exceptions Using std::nested_exception
In modern C++, handling errors across layered architectures often
requires preserving the original cause of a failure while adding
higher-level contextual information. The
std::nested_exception class, introduced in C++11, enables
developers to capture an active exception, wrap it inside a new
exception type, and recursively inspect the resulting chain of failures.
This article explains the mechanics of
std::nested_exception, demonstrating how to throw nested
exceptions using std::throw_with_nested and how to unpack
them with std::rethrow_if_nested.
The Problem of Context in Exception Handling
When an error occurs deep within a call stack—such as a failed file read or a dropped socket connection—catching and replacing it with a higher-level business logic exception often erases the original low-level diagnostic information. Conversely, letting the low-level exception propagate unchanged leaks implementation details to high-level callers.
std::nested_exception resolves this by acting as a
mixin-style wrapper that stores a std::exception_ptr to the
currently active exception, forming a linked list of exception
objects.
Creating a Nested Exception Chain
The standard library provides the helper function template
std::throw_with_nested to create nested exceptions without
manually subclassing std::nested_exception.
When called inside a catch block,
std::throw_with_nested(T&& t) checks if
T is a polymorphic class type not already derived from
std::nested_exception. If so, the compiler synthesizes a
type derived from both T and
std::nested_exception, captures the currently handled
exception via std::current_exception(), and throws the new
composite object.
#include <iostream>
#include <fstream>
#include <stdexcept>
#include <exception>
void readConfigFile(const std::string& path) {
std::ifstream file(path);
if (!file.is_open()) {
throw std::runtime_error("File not found: " + path);
}
}
void initializeSystem() {
try {
readConfigFile("config.json");
} catch (...) {
// Wraps the active exception inside a higher-level exception
std::throw_with_nested(std::runtime_error("System initialization failed"));
}
}Unpacking and Rethrowing Nested Exceptions
To access the earlier exceptions stored inside an exception chain,
C++ provides std::rethrow_if_nested. This function checks
whether the passed exception object derives from
std::nested_exception. If it does, the stored inner
exception is rethrown; if it does not, the function returns without
action.
By combining std::rethrow_if_nested with recursive
exception handlers, you can traverse the chain from the outermost
exception down to the root cause:
void printExceptionChain(const std::exception& e, int level = 0) {
std::cerr << std::string(level * 2, ' ') << "Error: " << e.what() << '\n';
try {
std::rethrow_if_nested(e);
} catch (const std::exception& nested) {
printExceptionChain(nested, level + 1);
} catch (...) {
std::cerr << std::string((level + 1) * 2, ' ') << "Unknown nested error.\n";
}
}
int main() {
try {
initializeSystem();
} catch (const std::exception& e) {
printExceptionChain(e);
}
return 0;
}When this program runs, the output clearly separates the layers:
Error: System initialization failed
Error: File not found: config.json
Key Technical Details
- Slicing Safety:
std::throw_with_nestedcreates a dynamic type combining the thrown type andstd::nested_exception. Because catching by reference (const std::exception&) preserves object identity, dynamic dispatch and dynamic casting insidestd::rethrow_if_nestedsucceed safely. - Value Types vs. Class Types: If the argument to
std::throw_with_nestedis a final class or not a non-union class type,std::throw_with_nestedsimply throws the argument directly without nesting. - No Active Exception: If
std::throw_with_nestedis invoked outside a catch block when no exception is active, it behaves identically to a normalthrow, with no nested context attached.