Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

c++ exception in destructor

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
like image 448
beetlej Avatar asked Dec 15 '16 22:12

beetlej


2 Answers

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.

like image 69
Brian Bi Avatar answered Nov 06 '22 11:11

Brian Bi


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.

like image 16
Pete Becker Avatar answered Nov 06 '22 12:11

Pete Becker