【问题标题】:When does compiler mark default generated constructor as noexcept?编译器何时将默认生成的构造函数标记为 noexcept?
【发布时间】:2016-02-10 14:56:31
【问题描述】:

似乎在至少某些编译器(GCC 5.2 和 Visual C++ 2015 Update 1)的现代版本中,当存在已初始化的类成员时,会错误地生成 noexcept 默认构造函数:

#include <memory>
#include <exception>
#include <iostream>

struct E {};

struct A
{
    A()
    {
        throw E();
    }
};

struct B
{
    A a;
};

struct C
{
    std::shared_ptr<B> b{ std::make_shared<B>() };
        //C() {}  // uncomment to fix
};

int main()
{
    try
    {
        new C;
    }
    catch (const E &)
    {
        std::cout << "Exception caught\n";
    }
    std::cout << "Exiting...\n";
}

运行此代码会导致在 GCC 5.2(C++14 模式)和 Visual C++ 2015 Update 1 上调用(而不是调用 catch 块)std::terminate

现场示例: http://coliru.stacked-crooked.com/view?id=16efc34ec173aca7

取消注释空构造函数可修复 Visual C++ 的此代码,但不能修复 GCC。无论如何,Clang 3.6 正确(我想?)调用 catch 块。

标准中是否有任何规则规定默认生成的构造函数何时必须标记为noexcept

【问题讨论】:

  • 如果您明确告诉生成默认构造函数 (C() = default;) 会发生什么?
  • 是的,有规则。从[except.spec]/17开始往上走。
  • 只是说=default 不会改变任何事情。我试过指定 C() noexcept(false)=default; 但这会产生语法错误
  • @T.C.它仅指特殊的成员函数,没有提及成员变量的构造函数。
  • 或许值得记住的是,规则在 C++14 之后发生了变化。 T.C. 链接的规则可能是也可能不是您的特定版本的编译器实现的。

标签: c++


【解决方案1】:

我在这里使用 C++14;我不知道 C++14 后的措辞变化是否澄清了这种情况。

问题在于,面对类内初始值设定项,生成 noexcept 规范的标准语言相当不清楚。该标准对 17p14 中生成的成员函数说:

f 允许所有异常,如果它直接调用的任何函数允许所有异常,f 具有异常规范 noexcept(true) 如果它直接调用的每个函数都不允许异常。

但是,“直接调用”在标准中没有明确定义,并且在涉及类内初始化程序时并不明显。您的类C 在其初始化程序中调用std::make_shared&lt;B&gt;(显然可以抛出不管B 的异常规范,因为它分配内存)和std::shared_ptr&lt;B&gt; 的复制构造函数(它是noexcept),但这些算作“直接调用",还是只计算复制构造函数?

这很可能是编译器在解释上的不同之处。 Clang 似乎计算了make_shared,而其他编译器显然没有。

B 一个默认构造函数应该不会改变任何东西,因为该构造函数只能在make_shared 中调用,因此肯定不在编译器的视野范围内;如果它确实改变了某些东西,那就是严重的错误。

但是,给C 一个空的、非默认的默认构造函数应该绝对意味着该构造函数不是noexcept,并且不同的行为肯定是一个错误。

【讨论】:

  • 如果 f 是默认构造函数或继承构造函数,则该集合还包含从大括号或相等初始化器初始化非静态数据成员的潜在异常集合的所有成员. - 来自 DR1351 的引用让我认为编译器在此处错误地生成了 noexcept 默认构造函数。看起来像一个错误。
【解决方案2】:

这似乎是与default member initializers 有关的编译器错误。请注意以下解决方法修复了 GCC:

struct C
{
    std::shared_ptr<B> b;
    C() : b{std::make_shared<B>()} {}
};

Demo

而所有这些仍然崩溃(注意我明确使用了noexecpt(false))。

struct A
{
    A() noexcept(false)
    {
        throw E();
    }
};

struct B
{
    A a;
    B() noexcept(false) {}
};

struct C
{
    std::shared_ptr<B> b{ std::make_shared<B>() };
    C() noexcept(false) {}
};

Demo

更进一步,保留默认初始化程序但用特定初始化程序覆盖它,也可以解决问题:

struct C
{
    std::shared_ptr<B> b{ std::make_shared<B>() };
    C() : b(std::make_shared<B>()) {}
};

Demo

因此,至少在我看来,这绝对是 GCC 中的一个错误。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-10-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-12
    • 2016-10-16
    • 2018-10-01
    • 1970-01-01
    相关资源
    最近更新 更多