在派生类的析构函数中抛出异常

Throwing exception in destructor of derived class

本文关键字:抛出异常 析构函数 派生      更新时间:2023-10-16

我正在编译一个c++库,我有一个析构函数和noexcept的问题。

我的理解是,从c++ 11开始,析构函数内部的noexcept(...)的默认值发生了变化。特别是,现在析构函数默认为noexcept(即noexcept(true))。

现在,我有一个库,由于某些类的析构函数可能抛出的事实,我得到许多编译器警告,但它没有标记为noexcept(false)。所以我决定改变这一点,并添加规范。然而,这些类继承自一些共同的父类Base,所以这迫使我将它也添加到父类析构函数中,否则我得到编译器错误overriding ‘virtual Base::~Base() noexcept

当然,我也可以将noexcept(false)添加到基类中(实际上,据我所知,在那里添加它就足够了),但是有很多类继承自Base,并且它们都继承noexcept(false)属性(据我所知)。然而,基类析构函数本身永远不会抛出,而且只有少数派生类可以抛出。因此,仅为少数类标记基类析构函数noexcept(false)似乎是一种浪费。

我有两个问题:

1)有什么办法解决这个问题吗?我不能删除抛出的几个派生类中的抛出,因为它们很重要。

2)我不确定由于将noexcept(false)添加到库中(实际上)所有类的基类中,编译器优化可能会降低多少。什么时候(如果有的话)我应该关心这件事?

编辑:在一个侧面说明,有没有办法改变noexcept的默认值在一个项目?也许是一个编译器选项?

编辑:因为有很多反馈,我应该编辑一下,加上一些注释:

1)库不是我写的,我当然也不打算重写许多类的析构函数。不得不将noexcept(false)添加到一些基类中已经足够痛苦了。而使用其他库也不是一种选择(出于编程之外的不同原因)。

2)当我说"我不能删除抛出的几个派生类中的抛出,因为它们很重要"时,我的意思是我相信代码应该在这种情况下终止,因为发生了一些非常糟糕的事情,并且无论如何都可能发生未定义的行为。这次投掷至少对发生的事情有一点解释。

3)我明白,抛出派生类的析构函数是不好的,因为它可能会泄漏,因为父类的析构函数没有被调用(这是正确的吗?)如果应该终止程序(以避免以后出现难以理解的bug),并且仍然让用户知道发生了什么,那么什么是一种干净的方法呢?

如果析构函数为虚函数,则派生析构函数不能为noexcept(false),除非基类析构函数也是noexcept(false)

想想看:虚函数的意义在于,即使调用者只知道基类,也可以调用派生类。如果在Base*上调用delete,如果Base承诺其析构函数不会抛出异常,则可以在没有任何异常处理代码的情况下编译该调用。如果指针指向一个Derived实例,而该实例的析构函数抛出异常,那就不好了。

尝试修改派生的析构函数,使其不会抛出异常。如果不可能,请将noexcept(false)添加到基类析构函数中。

我不确定将noexcept(false)添加到库中(实际上)所有类的基类中会降低多少编译器优化。什么时候(如果有的话)我应该关心这件事?

你不应该关心基于noexcept对析构函数声明的编译器优化。围绕noexcept声明的大多数优化来自于检测操作是noexcept并基于此执行更有效的操作的代码。即显式元编程。

为什么这对析构函数不重要?好吧,考虑c++标准库。如果给vector一个类型,而包含对象的析构函数之一抛出,则会得到未定义的行为。对于标准库中的所有都是如此(除非显式地标记)。

这意味着标准库不会费心检查你的析构函数是否为noexcept。它可以,而且几乎肯定会,假定没有析构函数发出异常。如果有的话……那是你的错。

因此,您的代码可能不会因为在析构函数上使用noexcept(false)而运行变慢。是否在基类的析构函数上使用noexcept(false)的决定不应该受到性能问题的影响。