【问题标题】:What does noexcept exactly encompass for constructors?noexcept 对构造函数究竟包含什么?
【发布时间】:2016-07-17 16:24:32
【问题描述】:

根据 C++ 标准,类构造函数上的 noexceptnoexcept-specification 究竟适用于什么?

  1. 函数体
    1. 在可选的 ctor-initializer 中初始化成员?
      1. 在可选的 mem-initializers 中初始化基类?
      2. 在可选的 mem-initializers 中初始化类成员?
    2. 复合语句
    3. 函数尝试块
  2. 对象基类的初始化未在 ctor-initializer 中初始化?
  3. 对象类成员的初始化未在 ctor-initializer 中初始化?
  4. 还有什么额外的吗?

换句话说,以上哪些被noexceptnoexcept-specification所包含(即在noexcept(true)抛出异常时触发std::terminate())?

请提供对标准的参考。也欢迎使用noexcept 为构造函数提供任何警告提示。谢谢!

【问题讨论】:

  • 我不是语言律师,但我的阅读方式是;如果 ctor 所做的任何事情(包括它初始化的对象的 ctor)都会引发异常;那么它不是noexcept

标签: c++ c++11 constructor language-lawyer noexcept


【解决方案1】:

换句话说,以上哪些被noexceptnoexcept-specification...?

异常规范(noexcept 和动态异常规范)与基类的构造、成员的构造和初始化以及构造函数主体中的代码有关。 基本上,在对象的构造过程中执行的所有函数 - 这是有道理的,因为异常规范与对象的构造函数相关联,因此它应该涵盖在对象构造期间执行的代码;如果这个结构的任何部分不包括在内,那将是违反直觉的。

支持标准引号...

如果在构造过程中抛出异常(并且可能未处理)怎么办?

[except.spec]/9

每当抛出 E 类型的异常并且搜索处理程序 ([except.handle]) 遇到具有不允许 E 的异常规范的函数的最外层块时,然后,

  • 如果函数定义具有动态异常规范,则调用函数std::unexpected() ([except.unexpected]),
  • 否则,将调用函数std::terminate() ([except.terminate])。

“函数的最外层块”是什么意思?函数体。1

上面的exception specification 包括noexcept-specification

隐式声明的异常规范如何在隐式声明的构造函数上确定?

[except.spec]/15

某个类 X 的隐式声明的特殊成员函数 f 被认为具有隐式异常规范,该规范由以下集合中的所有成员组成:

  • 如果f 是构造函数,

    • 构造函数调用的潜在异常集合

      • 对于X 的非变体非静态数据成员,
      • 用于X 的直接基类,以及
      • 如果X 是非抽象的([class.abstract]),对于X 的虚拟基类,

        (包括在此类调用中使用的默认参数表达式)由重载决议为 f ([class.ctor]) 的隐式定义选择...

    • 来自 brace-or-equal-initializers 的非静态数据成员初始化的潜在异常集未被忽略 ([class.base.init]);

这提供了非常有用的说明,说明编译器将使用什么来确定(并因此考虑被涵盖)异常规范。


1“函数的最外层块”是什么意思? 有人评论了对函数块定义的关注。该标准没有对函数块的正式定义。短语函数块仅用于Exception Handling [except]。该短语早在 C++98 就已包含在标准中。

为了进一步明确这一点,我们需要寻找替代来源并得出一些合理的结论。

来自Stroustrup C++ glossary

函数体 - 函数的最外层块。另见:try-block,函数定义。 TC++PL 2.7、13.

[dcl.fct.def.general]/1 开始,function-body 的语法涵盖了 ctor-initializercompound-statement功能尝试块;

函数定义有以下形式;

...

  函数体
ctor-initializeropt复合语句
函数尝试块

...

任何对函数体的非正式引用都应该被解释为对非终结符function-body...

的引用

同样重要的是要记住,异常规范与函数相关联,而不是与一般代码块(作用域块等)相关联。

鉴于异常处理子句和 Stroustrup 常见问题解答中短语的年龄,函数块函数体相同,标准可以可能与异常子句中使用的语言的更新有关。


一些经验证据,给定下面的代码,用于构造a1a2a3(当其他被注释掉时),导致std::terminate 被调用。结果适用于g++, clangMSVC

struct Thrower { Thrower() { std::cout << "throwing..." << std::endl; throw 42; } };

struct AsMember { Thrower t_; AsMember() noexcept : t_{} { std::cout << "ctor" << std::endl; } };

struct AsBase : Thrower { AsBase() noexcept { std::cout << "ctor" << std::endl; } };

struct AsNSDMI { Thrower t_ {}; AsNSDMI() noexcept { std::cout << "ctor" << std::endl; } };

int main()
{
    std::set_terminate([](){ std::cout << "terminating..." << std::endl; });
    try {
        //AsMember a1{};
        //AsBase a2{};
        AsNSDMI a3{};
    }
    catch (...) { std::cout << "caught..." << std::endl; }
    return 0;
}

【讨论】:

    【解决方案2】:

    如果您考虑一个 noexcept 构造函数,其定义在当前翻译单元中不可用(例如,构造函数实现驻留在共享库中),您可能可以自己回答这个问题。

    如果noexcept 不适用于整个构造函数,则构造函数仍可能以某种方式发出异常,从而违背了noexcept 的目的。

    noexcept 在这种情况下能够正常工作的唯一方法是它适用于整个构造函数,包括基类和成员初始化。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2012-08-18
      • 1970-01-01
      • 2012-01-01
      • 1970-01-01
      • 2017-11-03
      • 1970-01-01
      • 2015-03-04
      • 2016-02-19
      相关资源
      最近更新 更多