编译器何时将默认生成的构造函数标记为noexcept?

时间:2016-02-10 14:56:32

标签: c++

在初始化类成员时,至少有些编译器(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上调用std::terminate(而不是调用catch块)。

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

取消注释空构造函数会修复Visual C ++的此代码,但不会修复GCC。正确的Clang 3.6(我想?)在任何情况下都调用catch块。

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

2 个答案:

答案 0 :(得分:1)

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

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

  

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

然而,标准中没有明确定义“直接调用”,而对于类内初始化器则不明显。你的类C调用std::make_shared<B>(显然可以抛出,无论B的异常规范,因为它分配内存)和std::shared_ptr<B>的复制构造函数(在noizer中),但是那些算作“直接调用”,还是只复制构造函数计数?

这可能是编译器在解释上有所不同的地方。 Clang似乎计算make_shared,而其他编译器显然没有。{/ p>

B一个默认构造函数应该什么都不做,因为那个构造函数只在make_shared内调用,因此绝对不在编译器的视图之内;如果它确实改变了某些东西,就会出现严重错误。

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

答案 1 :(得分:1)

这似乎是一个与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中是一个错误。