So the way to nest exceptions in C++ using std::nested_exception
is:
void foo() {
try {
// code that might throw
std::ifstream file("nonexistent.file");
file.exceptions(std::ios_base::failbit);
}
catch(...) {
std::throw_with_nested(std::runtime_error("foo failed"));
}
}
But this technique uses explicit try/catch blocks at every level where one wishes to nest exceptions, which is ugly to say the least.
RAII, which Jon Kalb expands as "responsibility acquisition is initialization", is a much cleaner way to deal with exceptions instead of using explicit try/catch blocks. With RAII, explicit try/catch blocks are largely only used to ultimately handle an exception, e.g. in order to display an error message to the user.
Looking at the above code, it seems to me that entering foo()
can be viewed as entailing a responsibility to report any exceptions as std::runtime_error("foo failed")
and nest the details inside a nested_exception. If we can use RAII to acquire this responsibility the code looks much cleaner:
void foo() {
Throw_with_nested on_error("foo failed");
// code that might throw
std::ifstream file("nonexistent.file");
file.exceptions(std::ios_base::failbit);
}
Is there any way to use RAII syntax here to replace explicit try/catch blocks?
To do this we need a type that, when its destructor is called, checks to see if the destructor call is due to an exception, nests that exception if so, and throws the new, nested exception so that unwinding continues normally. That might look like:
struct Throw_with_nested {
const char *msg;
Throw_with_nested(const char *error_message) : msg(error_message) {}
~Throw_with_nested() {
if (std::uncaught_exception()) {
std::throw_with_nested(std::runtime_error(msg));
}
}
};
However std::throw_with_nested()
requires a 'currently handled exception' to be active, which means it doesn't work except inside the context of a catch block. So we'd need something like:
~Throw_with_nested() {
if (std::uncaught_exception()) {
try {
rethrow_uncaught_exception();
}
catch(...) {
std::throw_with_nested(std::runtime_error(msg));
}
}
}
Unfortunately as far as I'm aware, there's nothing like rethrow_uncaught_excpetion()
defined in C++.
main
is entering a responsibility to execute the program.sqrt
is entering a responsibility to compute the square root of something. But at its root, RAII is about tying certain behavior to whenever an object goes out of scope, regardless of why or how it goes out of scope. The entire point in RAII is that we don't need to know why an object is being destroyed (whether an exception was thrown), we just know it is being destroyed. – Cabbageworm