声明为noexcept的函数在其默认参数中引发异常时的奇怪行为

这是一个关于我的问题的例子:

struct B {
    B(B&&, int = (throw 0, 0)) noexcept {}
};

我知道这是一段非常奇怪的代码。它只是用来说明问题。的移动构造函数B有一个noexcept说明符,而它有一个抛出异常的默认参数。

如果我使用noexcept运算符来测试移动构造函数,它将返回false. 但是,如果我提供第二个参数,它将返回“true”(在 GCC 和 Clang 上):

noexcept( B(std::declval<B>()) );    // false
noexcept( B(std::declval<B>(), 1) ); // true

然后我添加了 class D,它继承自B但不提供移动构造函数。

struct D : public B { };

我测试了类D

noexcept( D(std::declval<D>()) );  // true

我已经阅读了标准,我认为根据标准,noexcept( D(std::declval<D>()) )应该返回false.

现在我试着按照标准来分析结果。

根据[expr.unary.noexcept]:

noexcept运算符的结果是,true除非表达式可能抛出([except.spec])。

所以现在我们需要判断表达式是否B(std::declval<B>())潜在抛出的

根据[except.spec]:

表达式E是潜在的抛出,如果

  • E是一个函数调用,其 ...,具有潜在抛出异常规范,或
  • E隐式调用具有潜在抛出异常规范的函数(例如 ...),或
  • E是一个 throw 表达式,或者
  • E是一个dynamic_cast表达式...
  • E是一个typeid表达式...
  • 任何的直接的子表达式Ë是潜在的投掷。

在我的例子中,该表达式调用了Bis的移动构造函数noexcept,所以它不属于前两种情况。显然,不属于后三种情况。

直接子表达式的定义在[intro.execution] 中:

表达式E直接子表达式是

  • E的操作数的组成表达式([expr.prop]),
  • E隐式调用的任何函数调用,
  • 如果E是 lambda 表达式,...
  • 如果Ë是一个函数调用或隐式调用的函数,每个的构成表达式默认参数([dcl.fct.default])在呼叫中,或者
  • 如果E创建了一个聚合对象...

根据该标准,默认参数(throw 0, 0)立即的子表达式B(std::declval<B>()),但不是立即的子表达式B(std::declval<B>(), 1),并且throw 0直接子表达式(throw 0, 0),这是一个潜在的投掷表达。So (throw 0, 0)andB(std::declval<B>())也是潜在的抛出表达式。noexcept( B(std::declval<B>()) )返回falsenoexcept( B(std::declval<B>(), 1) )返回是真的true

但我对最后一个例子感到困惑。为什么noexcept( D(std::declval<D>()) )返回trueD(std::declval<D>())will 隐式调用 的移动构造函数B,它满足立即子表达式的第二个要求。所以它也应该满足潜在投掷传递的要求。但结果恰恰相反。

那么我对前两个结果的原因的解释是否正确?第三个结果的原因是什么?


编辑:

标准中有类似的例子。在[except.spec] 中:

struct A {
  A(int = (A(5), 0)) noexcept;
  A(const A&) noexcept;
  A(A&&) noexcept;
  ~A();
};
struct B {
  B() noexcept;
  B(const B&) = default;        // implicit exception specification is noexcept(true)
  B(B&&, int = (throw 42, 0)) noexcept;
  ~B() noexcept(false);
};
int n = 7;
struct D : public A, public B {
    int * p = new int[n];
    // D?::?D() potentially-throwing, as the new operator may throw bad_­alloc or bad_­array_­new_­length
    // D?::?D(const D&) non-throwing
    // D?::?D(D&&) potentially-throwing, as the default argument for B's constructor may throw
    // D?::?~D() potentially-throwing
};

中的所有特殊成员函数A都是noexcept,而 的移动构造函数B是潜在抛出的,而 的析构函数Bnoexcept(false)

D此举构造受以下因素影响B的析构函数“?可能不是。因为D的拷贝构造函数也受B析构函数的影响,但是D的拷贝构造函数是不抛出的。

此外,根据[except.spec]:

即使在构造函数 ([except.ctor]) 的执行过程中抛出异常时调用完全构造子对象的析构函数,它们的异常规范对构造函数的异常规范没有贡献,因为从这样的析构函数会调用函数std?::?terminate而不是转义构造函数([except.throw]、[except.terminate])。

所以 的移动构造函数D真正受到 的移动构造函数的影响B

以上是声明为noexcept的函数在其默认参数中引发异常时的奇怪行为的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>