Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What does noexcept exactly encompass for constructors?

According to the C++ standard, what exactly does a noexcept noexcept-specification on a class constructor apply to?

  1. the function-body?
    1. initialization of members in the optional ctor-initializer?
      1. initialization of base classes in the optional mem-initializers?
      2. initialization of class members in the optional mem-initializers?
    2. the compound-statement?
    3. the function-try-block?
  2. initialization of object base classes not initialized in the ctor-initializer?
  3. initialization of object class members not initialized in the ctor-initializer?
  4. something additional?

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!

like image 778
jotik Avatar asked Mar 29 '16 21:03

jotik


1 Answers

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 allow E, 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 class X 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, and
      • if X is non-abstract ([class.abstract]), for X'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;
}
like image 98
Niall Avatar answered Oct 11 '22 14:10

Niall