在 C++ 中抛出后会调用析构函数吗?

Posted

技术标签:

【中文标题】在 C++ 中抛出后会调用析构函数吗?【英文标题】:Are destructors called after a throw in C++? 【发布时间】:2012-01-08 20:00:39 【问题描述】:

我运行了一个示例程序,确实调用了堆栈分配对象的析构函数,但这是否由标准保证?

【问题讨论】:

当然可以。 RAII,这是C++中最重要的习语之一,就靠这个了。 是的,这就是异常处理的重点。 @Jon & Kerrek SB,如果没有捕获到异常,则不保证会发生堆栈展开,它是由实现定义的:请参阅下面的 NPE 的答案,最后一部分是来自标准的引用这个。 【参考方案1】:

是的,保证(只要捕获到异常),直到调用析构函数的顺序

C++11 15.2 构造函数和析构函数[except.ctor]

1 当控制从 throw 表达式传递到处理程序时,将调用所有的析构函数 自进入 try 块以来构造的自动对象。这 自动对象以完成的相反顺序销毁 他们的建设。

此外,如果在对象构造过程中抛出异常,则保证部分构造对象的子对象被正确销毁:

2 任何存储持续时间的对象,其初始化或 破坏被异常终止将有析构函数 对其所有完全构造的子对象(不包括 类联合类的变体成员),也就是说,对于子对象 主构造函数 (12.6.2) 已完成执行并且 析构函数尚未开始执行。同样,如果 对象的非委托构造函数已完成执行,并且 该对象的委派构造函数以异常退出, 对象的析构函数将被调用。如果对象被分配在一个 new-expression,匹配的释放函数(3.7.4.2、5.3.4、 12.5),如果有的话,被调用来释放对象占用的存储空间。

整个过程称为“堆栈展开”:

3 为自动构造的对象调用析构函数的过程 从 try 块到 throw 表达式的路径称为“堆栈” 放松。”如果在堆栈展开期间调用的析构函数以 一个例外,调用 std::terminate (15.5.1)。

堆栈展开构成了广泛使用的称为Resource Acquisition Is Initialization (RAII) 的技术的基础。

请注意,如果未捕获到异常,则不一定要进行堆栈展开。在这种情况下,是否完成堆栈展开取决于实现。但无论堆栈展开是否完成,在这种情况下,您都可以保证最终调用 std::terminate

C++11 15.5.1 std::terminate() 函数[except.terminate]

2 ... 在没有找到匹配处理程序的情况下, 在调用std::terminate() 之前是否展开堆栈由实现定义。

【讨论】:

注意:关于被中断对象的构造。对象本身并没有被销毁(它实际上从未存在过),可以保证的是到目前为止已经完全构建的子部分(基类、属性)将以相反的顺序被销毁。 添加了有关未捕获异常的堆栈展开(或不展开)的信息。【参考方案2】:

是的,保证在堆栈展开时调用析构函数,包括由于抛出异常而展开。您必须记住的除了例外的技巧很少:

如果在其构造函数中抛出异常,则不会调用该类的析构函数。 如果在构造初始化列表捕获块中捕获异常,则会自动重新抛出异常。

【讨论】:

3) 析构函数从不抛出异常,因为没有没有方法可以充分处理它们。 @DevSolar 确实存在一些反例。 @AlfP.Steinbach:在堆栈展开期间抛出的任何析构函数(由于抛出另一个异常)将terminate()您的进程。我有兴趣看到反例...... @DevSolar:您(故意?)不清楚您想要反例的内容。但是关于第一个声明,即“析构函数永远不应该抛出异常”,一个不完全不常见的反例是一个表示函数结果的对象,如果调用者代码没有检查它是否表示失败,则从它的析构函数中抛出。另一个例子是一个事务保护对象,它从它的析构函数中抛出,除非嵌入它的代码已经成功地完成了它的努力(例如转移某物的所有权)并调用了它的release 方法。 @AlfP.Steinbach:“可以围绕它设计使用代码。” - 我同意。但是,经验法则是,您的对象可能会在您未预见到的上下文中使用,并且无法主动迎合。例如,如果您将抛出析构函数的对象放入 STL 向量中,并且该向量由于一些不相关的异常堆栈展开而被破坏,它将调用您的对象的析构函数,并且您的应用程序会“噗嗤”一声。当然,我可以从手榴弹上拔下别针,小心处理一会儿,然后把别针放回去。但我永远不应该那样做......【参考方案3】:

如果捕获到 throw,则 cpp 操作通常会继续。这包括析构函数和堆栈弹出。但是,如果未捕获异常,则无法保证堆栈弹出。

我的移动编译器也无法捕获裸抛出或空抛出。

示例:

#include <Jav/report.h>

int main()

 try  throw; 
 catch(...)  rep("I bet this is not caught"); 
 

【讨论】:

我给了这个加一。不仅没有被捕获,而且 try 块中的自动对象的析构函数(很容易插入)也没有被调用(g++ 7.4.0/clang++ 6.0.0 ubuntu),-std=c++[11|14|17 ]。似乎没有帮助将 noexcept 与 main 声明一起使用。我还尝试了相关的手册页选项。我可以在 catch 块中收到关于裸投的警告,但在 try 块中不会。如果我忽略了什么,请赐教。 @davernator 感谢您的加一。我希望你没有忽略任何高于我工资等级的东西。即使是现在。和平相处。

以上是关于在 C++ 中抛出后会调用析构函数吗?的主要内容,如果未能解决你的问题,请参考以下文章

为啥C++里面,析构函数会被调用两次

c++析构函数需要异常处理吗?如需要实现有何要求?

C++ 设置基类的析构函数为虚函数

C++的构造函数可以抛出异常么

信号处理以确保在 C++ 中调用析构函数

构造函数析构函数抛出异常的问题