Rethrowing and Nested Exceptions

Learn how we can rethrow errors, and wrap exceptions inside other exceptions using std::throw_with_nested and std::rethrow_if_nested.
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

DreamShaper_v7_hipster_Sidecut_hair_modest_clothes_fully_cloth_2.jpg
Ryan McCombe
Ryan McCombe
Posted

Within our catch blocks, we do not necessarily have to fully recover from the exception. We can create some code that ultimately (or conditionally) rethrows the exception. This lets us defer error handling to the calling code, elsewhere in the stack.

To rethrow an exception, we just use the throw statement within our catch statement:

To rethrow an exception, we just use the throw statement within our catch statement:

#include <iostream>
#include <exception>
using namespace std;

int Divide(int x, int y) {
  if (y == 0) throw logic_error {
    "Cannot divide by zero"
  };
  return x/y;
}

void CallFunction(int x, int y) {
  try {
    Divide(x, y);
  } catch (invalid_argument& e) {
    cout << "I have caught the exception" << endl;
    throw;
  }
}

int main() {
  try {
    CallFunction(2, 0);
  } catch (invalid_argument& e) {
    cout << "I have caught it too" << endl;
  }
  cout << "Goodbye";
}
I have caught the exception
I have caught it too
Goodbye

Preventing Data Loss when Rethrowing Exceptions

There is a common mistake when rethrowing exceptions. Rather than simply using the expression throw in isolation, we may be tempted to use it with the original error:

#include <iostream>

void func() {
  try {
    throw std::invalid_argument {
      "Useful Error Message"
    };
  } catch (std::exception& e) {
    std::cout << "First Catch: "
              << e.what() << std::endl;
    throw e;
  }
}

int main() {
  try {
    func();
  } catch (std::exception& e) {
    std::cout << "Second Catch: "
              << e.what()  << std::endl;
  }
}
First Catch: Useful Error Message
Second Catch: std::exception

Note the first catch statement has access to our custom message but, after rethrowing it, that information is lost.

This has the effect of throwing a copy of the exception, which is not desirable. Creating a copy unnecessarily has a performance impact, but worse, it can unintentionally change the type and lose information from the original.

That is exactly what is happening in the previous example. Our original exception had a type of std::invalid_argument. However, in our first catch statement, we are working with a base type -the more generic std::exception

Once we rethrow that, our second catch is now working with a simple std::exception, which doesn’t support custom error messages.

Unless we explicitly want to change the type of error that is thrown, we should simply use throw in isolation from within our catch statement, without being explicit about what it should throw:

#include <iostream>

void func() {
  try {
    throw std::invalid_argument {
      "Useful Error Message"
    };
  } catch (std::exception& e) {
    std::cout << "First Catch: "
              << e.what() << std::endl;
    throw;
  }
}

int main() {
  try {
    func();
  } catch (std::exception& e) {
    std::cout << "Second Catch: "
              << e.what()  << std::endl;
  }
}
First Catch: Useful Error Message
Second Catch: Useful Error Message

Slicing

Converting an instance of a derived class to a base class in this way is referred to as slicing. This is often a mistake - any data that the object had that does not exist in the base class is lost in this operation.

Nested Exceptions

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.

For this scenario, C++ has the concept of a nested exception. We throw the new exception, but include the previous exception within. So, the previous exception becomes nested in the new exception.

To do this, we use the std::throw_with_nested function, and we pass our new exception:

void ThrowNestedException() {
  try {
    throw std::runtime_error{"First Exception"};
  } catch (std::runtime_error& e) {
    std::throw_with_nested(
      std::logic_error{"Second Exception"}
    );
  }
}

We now have a std::logic_error, with a std::runtime_error nested within it.

We can catch the logic error in the usual way:

try {
  ThrowNestedException();
} catch (std::logic_error& e) {
  std::cout << "Caught a logic error: "
            << e.what();
}
Caught a logic error: Second Exception

When we used throw_with_nested, our original exception used multi-inheritance to gain a new type. In addition to its original type (a std::logic_error, in this case), our exception is now also a std::nested_exception.

To check if an exception is a std::exception, we can dynamic_cast it in the usual way:

auto nested {
  dynamic_cast<std::nested_exception*>(&e)
};

As with any dynamic_cast, if the argument was a std::nested_exception, a pointer is returned. If it wasn’t, a nullptr is returned.

So, we can use an if statement to check if our exception really was a nested exception:

try {
  ThrowNestedException();
} catch (std::logic_error& e) {
  std::cout << "Caught a logic error: "
            << e.what();

  auto nested {
    dynamic_cast<std::nested_exception*>(&e)
  };

  if (nested) {
    std::cout << "\nThere is a nested exception!\n";
  }
}
Caught a logic error: Second Exception
There is a nested exception!

Rethrowing Nested Exceptions

Once we’ve dealt with the new exception, we can get back to dealing with the original issue. In this example, that was the std::runtime_error

The standard way of doing this is to rethrow the nested exception. We can do this from the nested_exception* returned from the dynamic cast, by calling rethrow_nested:

try {
  ThrowNestedException();
} catch (std::logic_error& e) {
  std::cout << "Caught a logic error: "
            << e.what();

  auto nested{
    dynamic_cast<std::nested_exception*>(&e)
  };

  if (nested) {
    std::cout << "\nThere is a nested exception!\n";
    try {
      nested->rethrow_nested();
    } catch (std::runtime_error& e) {
      std::cout << "Caught a runtime error: "
                << e.what();
    }
  }
}
Caught a logic error: Second Exception
There is a nested exception!
Caught a runtime error: First Exception

This dynamic cast and rethrow pattern is so common that the standard library provides a method for us to make our code a little simpler. We can just pass our outer exception to the std::rethrow_if_nested function:

try {
  ThrowNestedException();
} catch (std::logic_error& e) {
  std::cout << "Caught a logic error: "
            << e.what();

  try {
    std::rethrow_if_nested(e);
  } catch (std::runtime_error& e) {
    std::cout << "\nThere is a nested exception!";
              << "\nCaught a runtime error: "
              << e.what();
  }
}
Caught a logic error: Second Exception
There is a nested exception!
Caught a runtime error: First Exception

In these simplified examples, we are throwing and catching the error immediately. But, as we covered in our previous lesson covering how exceptions interact with the call stack, this need not be the case.

We don’t need to wrap our rethrow in a nested try statement - we can just throw our error, and let it be caught anywhere else on the call stack.

try {
  ThrowNestedException();
} catch (std::logic_error& e) {
  std::cout << "Caught a logic error: "
            << e.what();
  std::rethrow_if_nested(e);
}

Was this lesson useful?

Ryan McCombe
Ryan McCombe
Posted
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Exceptions and Error Handling
7a.jpg
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, unlimited access!

This course includes:

  • 106 Lessons
  • 550+ Code Samples
  • 96% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

C++ Function Try Blocks

Learn the basics of function try blocks in C++ with this beginner's guide. Discover how to catch exceptions thrown within a function and gracefully handle errors in your code.
5a.jpg
Contact|Privacy Policy|Terms of Use
Copyright © 2023 - All Rights Reserved