From other threads, I know we should not throw exception in destructor! But for below example, it really works. Does that means we can only throw exception in one instance's destructor? how should we understand this code sample!
#include <iostream>
using namespace std;
class A {
public:
~A() {
try {
printf("exception in A start\n");
throw 30;
printf("exception in A end\n");
}catch(int e) {
printf("catch in A %d\n",e);
}
}
};
class B{
public:
~B() {
printf("exception in B start\n");
throw 20;
printf("exception in B end\n");
}
};
int main(void) {
try {
A a;
B b;
}catch(int e) {
printf("catch in main %d\n",e);
}
return 0;
}
The output is:
exception in B start
exception in A start
catch in A 30
catch in main 20
Best practice prior to C++17 says to not let exceptions propagate out of a destructor. It is fine if a destructor contains a throw
expression or calls a function that might throw, as long as the exception thrown is caught and handled instead of escaping from the destructor. So your A::~A
is fine.
In the case of B::~B
, your program is fine in C++03 but not in C++11. The rule is that if you do let an exception propagate out of a destructor, and that destructor was for an automatic object that was being directly destroyed by stack unwinding, then std::terminate
would be called. Since b
is not being destroyed as part of stack unwinding, the exception thrown from B::~B
will be caught. But in C++11, the B::~B
destructor will be implicitly declared noexcept
, and therefore, allowing an exception to propagate out of it will call std::terminate
unconditionally.
To allow the exception to be caught in C++11, you would write
~B() noexcept(false) {
// ...
}
Still, there would be the issue that maybe B::~B
is being called during stack unwinding---in that case, std::terminate
would be called. Since, before C++17, there is no way to tell whether this is the case or not, the recommendation is to simply never allow exceptions to propagate out of destructors. Follow that rule and you'll be fine.
In C++17, it is possible to use std::uncaught_exceptions()
to detect whether an object is being destroyed during stack unwinding. But you had better know what you're doing.
The recommendation that "we should not throw exception in destructor" is not an absolute. The issue is that when an exception is thrown the compiler starts unwinding the stack until it finds a handler for that exception. Unwinding the stack means calling destructors for objects that are going away because their stack frame is going away. And the thing that this recommendation is about occurs if one of those destructors throws an exception that isn't handled within the destructor itself. If that happens, the program calls std::terminate()
, and some folks think that the risk of that happening is so severe that they have to write coding guidelines to prevent it.
In your code this isn't a problem. The destructor for B
throws an exception; as a result, the destructor for a
is also called. That destructor throws an exception, but handles the exception inside the destructor. So there's no problem.
If you change your code to remove the try ... catch
block in the destructor of A
, then the exception thrown in the destructor isn't handled within the destructor, so you end up with a call to std::terminate()
.
EDIT: as Brian points out in his answer, this rule changed in C++11: destructors are implicitly noexcept
, so your code should call terminate
when the the B
object is destroyed. Marking the destructor as noexcept(false)
"fixes" this.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With