According to the C++ standard, what exactly does a noexcept
noexcept-specification on a class constructor apply to?
In other words, which of the above are encompassed by the noexcept
noexcept-specification (i.e. trigger std::terminate()
when throwing an exception if noexcept(true)
)?
Please provide references to the standard. Tips on any caveats using noexcept
for constructors are also welcome. Thanks!
In other words, which of the above are encompassed by the
noexcept
noexcept-specification...?
The exception specification (noexcept
and dynamic exception specification) pertains to the construction of the base classes, construction and initialisation of the members, and the code in the body of the constructor. Basically, all functions executed in the construction of the object - this makes sense in that the exception specification is tied to the constructor of the object, hence it should cover code executed during the construction of the object; it would be counterintuitive if any part of the construction was not covered by this.
Supporting standard quotes...
What if an exception is thrown during construction (and possibly not handled)?
[except.spec]/9
Whenever an exception of type
E
is thrown and the search for a handler ([except.handle]) encounters the outermost block of a function with an exception specification that does not allowE
, then,
- if the function definition has a dynamic-exception-specification, the function
std::unexpected()
is called ([except.unexpected]),- otherwise, the function
std::terminate()
is called ([except.terminate]).
What does the "outermost block of a function" mean? The body of the function.1
The exception specification above includes the noexcept-specification.
How is the implicitly declared exception specification determined on an implicitly declared constructor?
[except.spec]/15
An implicitly-declared special member function
f
of some classX
is considered to have an implicit exception specification that consists of all the members from the following sets:
if
f
is a constructor,
the sets of potential exceptions of the constructor invocations
- for
X
's non-variant non-static data members,- for
X
's direct base classes, andif
X
is non-abstract ([class.abstract]), forX
's virtual base classes,(including default argument expressions used in such invocations) as selected by overload resolution for the implicit definition of f ([class.ctor])...
the sets of potential exceptions of the initialization of non-static data members from brace-or-equal-initializers that are not ignored ([class.base.init]);
This provides very useful clarification on what the compiler would use to determine (and hence consider covered by) the exception specification.
1What does the "outermost block of a function" mean? There was a comment on the concern to the definition of the block of a function. The standard has no formal definition of the block of a function. The phrase block of a function is only used in the Exception Handling [except]. The phrase was included in the standard from as far back as C++98.
For further clarity on this, we would need seek alternative source and draw some reasonable conclusions.
From Stroustrup C++ glossary;
function body - the outermost block of a function. See also: try-block, function definition. TC++PL 2.7, 13.
And from [dcl.fct.def.general]/1 the grammar for the function-body that covers the ctor-initializer with compound-statement and the function-try-block;
Function definitions have the form;
...
function-body:
ctor-initializeroptcompound-statement
function-try-block...
Any informal reference to the body of a function should be interpreted as a reference to the non-terminal function-body...
It is also important to bear in mind that exception specifications are associated with functions and not general blocks of code (scoped blocks etc.).
Given the age of the phrase in the exception handling clause and the Stroustrup FAQ, the block of a function is the same as the function-body, the standard could probably do with an update of the language used in the exception clause.
Some empirical evidence, given the code below, for the construction of a1
, a2
and a3
(when the others are commented out), result in std::terminate
being called. The result is applicable for g++, clang and MSVC.
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;
}
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