默认析构函数不抛出异常

34

以下代码在gcc-4.7.1下无法编译,但在clang-3.2下可以编译。哪一个符合C++11标准?

struct X {
  virtual ~X() = default;
};

struct Y : X {
  virtual ~Y() = default;
};

gcc-4.7.1报错:

looser throw specifier for 'virtual Y::~Y()'
error: overriding 'virtual X::~X() noexcept(true)'

显然,gcc-4.7.1认为X的默认析构函数是nothrow的,但Y的默认析构函数不是nothrow的。为什么会这样呢?有谁能引用标准中正确的位置吗?谢谢。

我在stackoverflow上看到类似的问题,但没有看到答案参考了标准。


1
如果你的代码与问题中的代码完全相同,那么很可能是编译器中的一个错误。 - David Rodríguez - dribeas
1
请注意,g++快照20120708可以正确编译此代码。 - Paul Preney
今天在尝试将某个东西移植到Debian g++4.7时,遇到了一个很好的问题。 - vsoftco
1个回答

39
编译器在这里面陷入了两难境地,原因如下:
1. 在函数声明中不指定任何异常(即不使用throw或noexcept(相当于noexcept(true)))意味着允许该函数抛出所有可能的异常:
(§15.4/12, 强调我的)没有异常规范或形式为noexcept(constant-expression)的异常规范,其中constant-expression产生false允许所有异常。
2. 默认析构函数必须允许其隐式定义直接调用的函数允许的异常:
(§15.4/14, 强调我的)隐式声明的特殊成员函数(Clause 12)应具有异常规范。如果f是一个隐式声明的默认构造函数、复制构造函数、移动构造函数、析构函数、复制赋值运算符或移动赋值运算符,则它的隐式异常规范只有当T被允许通过f的隐式定义直接调用的函数的异常规范时才指定类型标识符T;如果任何函数直接调用f允许所有异常,则f应允许所有异常,如果每个函数直接调用f允许没有异常,则f不应允许任何异常。
3. 当特殊成员(如析构函数)被显式地设置为默认值时,即当您使用= default时,异常规范是可选的(请参见下面的"可能具有"):
(8.4.2/2, 强调我的)显式默认函数[...]只有在与隐式声明上的异常规范兼容(15.4)时,才能具有显式异常规范。[...]
标准中没有要求在显式默认析构函数中指定异常规范。 结论:因此,不指定显式默认析构函数的异常可以有两种解释:
1. 意味着允许所有异常(根据上述1)。
2. 或者,作为替代方案,意味着允许与隐式默认析构函数允许的相同异常一样允许的异常(根据上述3),在您的情况下意味着允许没有异常(根据上述2)。

很遗憾,在您的基类声明的情况下,GCC会以一种方式(支持“没有异常”)解决这个困境,并以不同的方式在派生类的情况下(支持“所有异常”)。

我认为这种明显含糊不清的情况的最自然解释是假定(2)和(3)覆盖(1)。虽然标准没有如此规定,但它应该这样做。根据这种解释,Clang 在这里似乎是正确的。


3
很好的回答,实际上问题非常重要,因为你可能会使用CRTP模式声明一个具有=default受保护析构函数的singleton,然后所有g++编译器<=4.7都会输出相同的“looser throw…”错误。然而对于g++>=4.8,代码可以编译得很好。我的解决方案是将析构函数写成virtual ~Destructor(){};,以便实现可移植的代码。虽然不太优雅,但适用于g++ 4.7及更高版本。 - vsoftco

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接