简体   繁体   中英

When does compiler mark default generated constructor as noexcept?

It seems like at modern versions of at least some compilers (GCC 5.2 and Visual C++ 2015 Update 1) incorrectly generate noexcept default constructors when there are initialized class members:

#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";
}

Running this code causes std::terminate to be called (instead of invoking catch block) on GCC 5.2 (C++14 mode) and Visual C++ 2015 Update 1.

Live example: http://coliru.stacked-crooked.com/view?id=16efc34ec173aca7

Uncommenting empty constructor fixes this code for Visual C++, but not for GCC. Clang 3.6 correctly (I suppose?) calls catch block in any case.

Are there any rules in Standard that tell when the default generated constructor must be marked as noexcept ?

I'm going by C++14 here; I don't know if post-C++14 wording changes clarified the situation.

The problem is that the standard's language for generated noexcept specifications in the face of in-class initializers is rather unclear. The standard says for generated member functions in 17p14:

f allows all exceptions if any function it directly invokes allows all exceptions, and f has the exception-specification noexcept(true) if every function it directly invokes allows no exceptions.

However, "directly invokes" is not clearly defined in the standard, and is not obvious when it comes to in-class initializers. Your class C invokes std::make_shared<B> (which obviously can throw regardless of B's exception specification, as it allocates memory) and the copy constructor of std::shared_ptr<B> (which is noexcept) in its initializer, but do those count as "directly invoked", or does only the copy constructor count?

It may well be that this is where the compilers differ in interpretation. Clang appears to count make_shared , while the other compilers apparently don't.

Giving B a default constructor should change nothing, since that constructor is only called from within make_shared and is therefore definitely out of the view of the compiler; if it does change something, there's something seriously wrong.

However, giving C an empty, non-defaulted default constructor should most definitely mean that the constructor is not noexcept, and different behavior is definitely a bug.

It seems that this is a compiler bug that has to do with default member initializers . Note the following workaround fixes GCC:

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

Demo

whereas all of this still crashes (note I explicitly used 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

even further, keeping the default initializer but overriding it with a specific one, also fixes things:

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

Demo

So it definitely seems to me to be a bug in GCC, at least.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM