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

  1. Slicing Safety: std::throw_with_nested creates a dynamic type combining the thrown type and std::nested_exception. Because catching by reference (const std::exception&) preserves object identity, dynamic dispatch and dynamic casting inside std::rethrow_if_nested succeed safely.
  2. Value Types vs. Class Types: If the argument to std::throw_with_nested is a final class or not a non-union class type, std::throw_with_nested simply throws the argument directly without nesting.
  3. No Active Exception: If std::throw_with_nested is invoked outside a catch block when no exception is active, it behaves identically to a normal throw, with no nested context attached.