当构造函数抛出异常时,RAII如何工作?

new*_*mer 19 c++ constructor exception raii

我正在学习C++中的RAII习语,以及如何使用智能指针.

在我的阅读中,我遇到了两件对我来说似乎相互矛盾的事情.

引用自http://www.hackcraft.net/raii/:

...如果已经创建了具有RAII语义的成员对象并且在构造函数完成之前发生了异常,那么它的析构函数将作为堆栈展开的一部分被调用.因此,即使没有使用成员RAII对象完全构造,控制多个资源的对象也可以保护它们的清理.

但引自http://www.parashift.com/c++-faq-lite/exceptions.html#faq-17.10:

如果构造函数抛出异常,则不会运行该对象的析构函数.如果您的对象已经完成了需要撤消的操作(例如分配一些内存,打开文件或锁定信号量),则必须通过对象内的数据成员记住这些"需要撤消的内容".

然后第二个链接源建议使用智能指针来处理已在构造函数中分配的事物的问题.

那么这些场景中究竟发生了什么?

Nic*_*las 15

你误解了第一句话.这并不难,因为它令人困惑.

如果已经创建了具有RAII语义的成员对象,并且在构造函数完成之前发生了异常,那么它的析构函数将作为堆栈展开的一部分进行调用.

这就是它所说的.这就是它的意思:

如果已创建具有RAII语义的成员对象,并且在外部对象的构造函数完成之前外部对象中发生异常,成员对象的析构函数将作为堆栈展开的一部分进行调用.

看到不同?这个想法是成员对象完成了它的构造函数,但拥有的类型却没有.它在构造函数中抛出某个地方(或者在该构造函数之后初始化的另一个成员的构造函数).这将导致所有成员的析构函数被调用(所有已完成构造的构造,即),而不是它自己的析构函数.

这是一个例子:

class SomeType
{
  InnerType val;
public:
  SomeType() : val(...)
  {
    throw Exception;
  }
};
Run Code Online (Sandbox Code Playgroud)

创建SomeType实例时,它将调用InnerType::InnerType.只要不抛出,它就会进入SomeType构造函数.当它抛出时,它将导致val被破坏,从而召唤InnerType::~InnerType.

  • "这将导致所有成员的析构函数被调用,而不是它自己的析构函数." - 好吧,只是在异常被抛出之前已完成建设的那些...... (3认同)

tem*_*def 5

这里没有矛盾; 在不同的环境中使用了一些令人困惑的术语.

如果对象的构造函数抛出异常,则会发生以下情况(假设捕获到异常):

  1. 构造函数中的所有局部变量都会调用它们的析构函数,释放它们获取的所有资源(如果有的话).
  2. 构造函数抛出异常的对象的所有直接子对象都将调用它们的析构函数,释放它们已获取的资源(如果有的话).
  3. 构造函数抛出的对象的所有基类都将调用它们的析构函数(因为它们是在派生类构造函数运行之前完全构造的)
  4. 将进行来自呼叫者等的进一步清理.

因此,由智能指针或作为被破坏对象的数据成员的其他RAII对象管理的任何资源都将被清除,但是在对象的析构函数中执行清理的专用代码将不会触发.

希望这可以帮助!