如何正确检查C++析构函数是否为noexcept?
核心规则:C++析构函数的默认noexcept属性
C++标准明确规定:析构函数默认是noexcept(true),仅在以下两种情况会变为noexcept(false):
- 显式声明为
~T() noexcept(false); - 类的基类或非静态成员的析构函数是
noexcept(false),此时当前类析构函数的noexcept会被自动推导为false。
注意:析构函数的noexcept属性由声明决定,和函数体内部是否实际抛出异常无关。这是析构函数和普通成员函数的关键区别——普通成员函数默认是noexcept(false)。
逐个解释示例中的断言问题
1. static_assert(is_noexcept_destructible<Baddy>); 结果为true
自定义的is_noexcept_destructible通过noexcept(std::declval<T&&>().~T())判断,而Baddy的析构函数未显式声明noexcept,默认是noexcept(true),因此结果符合标准。
2. static_assert(std::is_nothrow_destructible_v<Baddy>); 结果为true
std::is_nothrow_destructible_v的作用是判断类型的析构函数是否能被noexcept调用。由于Baddy的析构函数默认noexcept(true),因此返回true。该特性仅关注析构函数的声明属性,不关心函数体是否实际抛出异常。
3. static_assert(not noexcept(Baddy().~Baddy())); 结果为false
Baddy()创建临时对象后调用析构函数,因析构函数默认noexcept(true),noexcept(Baddy().~Baddy())的结果为true,取反后为false,断言失败。这是标准规定的行为,和函数体内部抛出异常无关。
4. static_assert(not noexcept(Baddy().fn())); 结果为false(你认为的“universally false”)
普通成员函数默认是noexcept(false),因此noexcept(Baddy().fn())的结果应为false,取反后为true,断言本应通过。你看到的失败可能是编译器特殊行为或代码书写误差,但从标准规则来看,这个断言的预期逻辑有误。
5. static_assert(not noexcept(Gooddy().~Gooddy())); 结果为false
Gooddy的析构函数显式声明为noexcept(true),因此noexcept(Gooddy().~Gooddy())的结果为true,取反后为false,断言失败是符合预期的——析构函数确实是noexcept(true)。
6. static_assert(not noexcept(Gooddy().fn())); 结果为false
Gooddy::fn()显式声明为noexcept(true),因此noexcept(Gooddy().fn())的结果为true,取反后为false,断言失败是正确的,和函数的实际noexcept属性一致。
关于std::declval的疑问
std::declval对析构函数的判断是有效的,之所以看起来“失效”,是因为你误解了析构函数的默认noexcept规则——它默认是true,而非普通成员函数的默认false。
内容的提问来源于stack exchange,提问作者mariusm

