Whilst handling an exception within a catch
block, there may be scenarios where yet another exception can occur, thus leaving us with two exceptions being active at the same time.
To handle this, we have the concept of a nested exception. This allows us to throw a new exception, and for that exception to include a reference to the original. So, the original exception is said to be nested within the new exception.
To do this, we use the std::throw_with_nested()
function, passing in our new exception:
// Auth.h
#pragma once
#include <string>
class AuthenticationError{/*...*/}
class SecurityError : public std::exception {};
void Auth(
std::string Email, std::string Password) {
throw AuthenticationError{Email, Password};
}
void Login(
std::string Email, std::string Password) {
try {
Auth(Email, Password);
} catch (AuthenticationError& e) {
std::throw_with_nested(SecurityError{});
}
}
Above, our Login()
function will emit a SecurityError
, with an AuthenticationError
nested within it. We can catch the SecurityError
in the normal way:
#include <iostream>
#include "Auth.h"
int main() {
try {
Login("test@example.com", "secret");
} catch (SecurityError& e) {
std::cout << "Caught a SecurityError";
}
std::cout << "\nProgram recovered";
}
When we handle the exception in this way, we also handle any nested exceptions that it might have contained. As a result, our program fully recovers:
Caught a SecurityError
Program recovered
Typically, however, this is not what we want. If we’re using nested exceptions in our project, we need to consider the possibility that the exceptions we receive might contain nested exceptions that also need to be handled. We cover techniques on how to do this in the next few sections.
The throw_with_nested()
function implements nested exceptions using multiple inheritance, meaning a class can inherit from multiple base classes. This is a technique we’ll cover in detail later in the course.
For now, we can think of the object created by throw_with_nested()
as having two types. One of the types is std::nested_exception
, and the other is the type of object we provided as an argument. In the previous example, that was our user-defined type SecurityError
:
std::throw_with_nested(SecurityError{});
To check if our type is a std::nested_exception
, we can dynamic_cast
it in the usual way:
#include <iostream>
#include "Auth.h"
int main() {
try {
Login("test@example.com", "secret");
} catch (SecurityError& e) {
std::cout << "Caught a SecurityError";
auto NestedException{
dynamic_cast<std::nested_exception*>(&e)};
if (NestedException) {
std::cout <<
"\nThere's a nested exception in here";
}
}
std::cout << "\nProgram recovered";
}
Caught a SecurityError
There's a nested exception in here
Program recovered
However, the easiest way to detect and deal with nested exceptions tends to be to rethrow them.
Once we’ve dealt with the outer exception, we can decide to rethrow the nested exception, if there is one.
The easiest way to do this is through the std::rethrow_if_nested()
 function:
#include <iostream>
#include "Auth.h"
int main() {
try {
Login("test@example.com", "secret");
} catch (SecurityError& e) {
std::cout << "Caught a SecurityError";
try {
std::rethrow_if_nested(e);
} catch (...) {
std::cout << "\nThere's a nested "
"exception in here";
}
}
std::cout << "\nProgram recovered";
}
Caught a SecurityError
There's a nested exception in here
Program recovered
As we might expect, we can handle a nested exception like any other. Our catch
block can specify the exception type it is capable of handling:
#include <iostream>
#include "Auth.h"
int main() {
try {
Login("test@example.com", "secret");
} catch (SecurityError& e) {
std::cout << "Caught a SecurityError";
try {
std::rethrow_if_nested(e);
} catch (AuthenticationError& e) {
std::cout
<< "\nNested AuthenticationError: "
<< e.Email;
}
}
std::cout << "\nProgram recovered";
}
Caught a SecurityError
Nested AuthenticationError: test@example.com
Program recovered
Also, we don’t need to handle nested exceptions locally. We can just throw them and let a function elsewhere on the call stack deal with it:
void SomeFunction() {
try {
Login("test@example.com", "secret");
} catch (SecurityError& e) {
std::cout << "Caught a SecurityError";
std::rethrow_if_nested(e);
}
}
Exceptions can be nested arbitrarily deeply. For example, in a complex project that uses nested exceptions, we can have an exception, nested within an exception, nested within another exception.
The contrived GenerateExceptions()
function simulates this scenario:
#include <iostream>
void GenerateExceptions() {
try {
throw std::runtime_error{"Error One"};
} catch (...) {
try {
std::throw_with_nested(
std::runtime_error{"Error Two"});
} catch (...) {
std::throw_with_nested(
std::runtime_error{"Error Three"});
}
}
}
When we catch an exception, we’ll often want to unwind all of the exceptions contained within it. In the following example, the HandleExceptions()
function demonstrates how we can do this:
#include <iostream>
void GenerateExceptions() {/*...*/}
void HandleExceptions(const std::exception& e) {
std::cout << "\nHandling " << e.what();
try {
std::rethrow_if_nested(e);
} catch (const std::exception& Nested) {
HandleExceptions(Nested); // Recursion
}
}
int main() {
try {
GenerateExceptions();
} catch (std::exception& e) {
HandleExceptions(e);
}
}
Handling Error Three
Handling Error Two
Handling Error One
This is an example of a recursive function. A recursive function conditionally calls itself.
In this case, HandleException()
calls itself if std::rethrow_if_nested(e);
throws an exception. In other words, it calls itself if the exception it received as a parameter contains a nested exception.
This works because each call to HandleExceptions()
catches and handles the current nested exception, then calls itself again with the next nested exception. This process repeats, unwinding the nested exceptions from the inside out, until std::rethrow_if_nested(e)
doesn't throw anymore, indicating there are no more nested exceptions.
Recursion is a relatively unintuitive concept, so don’t worry if this examples doesn’t make sense. We cover recursion starting with simpler examples in a dedicated lesson later in the course.
This lesson provided an exploration of nested exceptions, demonstrating their implementation, handling, and practical applications. The key topics we learned included:
std::throw_with_nested()
for handling multiple layers of exceptions.dynamic_cast
and std::nested_exception
.std::rethrow_if_nested()
and handling them appropriately.Learn about nested exceptions in C++: from basic concepts to advanced handling techniques
Comprehensive course covering advanced concepts, and how to use them on large-scale projects.