析构函数

时间:2016-12-15 22:13:08

标签: c++ exception-handling

从其他线程,我知道我们不应该在析构函数中抛出异常!但是对于下面的例子,它确实有效。这是否意味着我们只能在一个实例的析构函数中抛出异常?我们该如何理解这个代码示例!

#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;
}

输出结果为:

exception in B start
exception in A start
catch in A 30
catch in main 20

2 个答案:

答案 0 :(得分:16)

C ++之前的最佳实践17表示不允许异常从析构函数中传播 。如果析构函数包含throw表达式或调用可能抛出的函数,只要抛出异常被捕获并处理而不是从析构函数中转义,则可以。所以你的A::~A没问题。

B::~B的情况下,您的程序在C ++ 03中很好,但在C ++ 11中却没有。规则是,如果你让一个异常从析构函数中传播出来,并且析构函数是一个自动对象,它被直接通过堆栈展开而被破坏,那么{{ 1}}会被调用。由于std::terminate未作为堆栈展开的一部分被销毁,因此将捕获从b引发的异常。但是在C ++ 11中,B::~B析构函数将被隐式声明为B::~B,因此,允许异常传播出来将无条件地调用noexcept

要允许在C ++ 11中捕获异常,您可以编写

std::terminate

仍然存在可能在堆栈展开期间调用~B() noexcept(false) { // ... } 的问题 - 在这种情况下,将调用B::~B。因为,在C ++ 17之前,无法判断是否是这种情况,建议绝不允许异常传播出析构函数。遵循这条规则,你会没事的。

在C ++ 17中,可以使用std::terminate来检测在堆栈展开期间是否正在销毁对象。但你最好知道自己在做什么。

答案 1 :(得分:6)

建议&#34;我们不应该在析构函数中抛出异常&#34;不是绝对的。问题是当抛出异常时,编译器开始展开堆栈,直到它找到该异常的处理程序。展开堆栈意味着调用析构函数来处理因为堆栈框架消失而丢失的对象。如果其中一个析构函数抛出了一个在析构函数本身内没有处理的异常,那么就会发生这个建议。如果发生这种情况,程序会调用std::terminate(),有些人认为发生这种情况的风险非常严重,以至于他们必须编写编码指南以防止它发生。

在您的代码中,这不是问题。 B的析构函数抛出异常;结果,也调用了a的析构函数。析构函数抛出异常,但处理析构函数内的异常。所以没有问题。

如果更改代码以删除try ... catch的析构函数中的A块,则析构函数中抛出的异常不会在析构函数中处理,因此您最终会得到一个致电std::terminate()

编辑:正如Brian在他的回答中指出的那样,这个规则在C ++ 11中发生了变化:析构函数是隐式的noexcept,所以你的代码应该在terminate对象时调用B被毁了。将析构函数标记为noexcept(false)&#34;修复&#34;此