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
?
Inheriting constructors and the implicitly-declared default constructors, copy constructors, move constructors, destructors, copy-assignment operators, move-assignment operators are all noexcept(true) by default, unless they are required to call a function that is noexcept(false) , in which case these functions are ...
A noexcept-expression is a kind of exception specification: a suffix to a function declaration that represents a set of types that might be matched by an exception handler for any exception that exits a function.
The noexcept operator performs a compile-time check that returns true if an expression is declared to not throw any exceptions. It can be used within a function template's noexcept specifier to declare that the function will throw exceptions for some types but not others.
The noexcept specification is part of the function type but can not be used for function overloading. There are two good reasons for the use of noexcept: First, an exception specifier documents the behaviour of the function. If a function is specified as noexcept, it can be safely used in a non-throwing function.
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, andf
has the exception-specificationnoexcept(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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With