Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Marking a function `noexcept` that could cause an exception constructing the returned object

Tags:

Consider this function:

std::vector<unsigned> copy(std::vector<unsigned> const& v) noexcept
{ return v; }

int main()
{
   try {
       (void)copy({1, 2, 3});
   } catch(...) {}
}

The construction by copy of the returned object could throw. In this case, would the exception be propagated to the caller (i.e. it's considered to happen in main) and thus would be handled in the catch(...) handler? Or would the exception run into noexcept and lead to invoking std::terminate()?

Have the changes about lifetimes rules in C++17/C++20 (standardized RVO, temporary materialization, implicit object creation, etc) changed some rules in this regard with respect to previous versions of the standard?

like image 401
Peregring-lk Avatar asked Sep 17 '20 19:09

Peregring-lk


People also ask

What happens if Noexcept function throws exception?

If any functions called between the one that throws an exception and the one that handles the exception are specified as noexcept , noexcept(true) (or throw() in /std:c++17 mode), the program is terminated when the noexcept function propagates the exception.

Is Noexcept useful?

The primary use of noexcept is for generic algorithms, e.g., when resizing a std::vector<T> : for an efficient algorithm moving elements it is necessary to know ahead of time that none of the moves will throw. If moving elements might throw, elements need to be copied instead.


2 Answers

C++17 had a wording change that added sequencing around the return statement. The following paragraph was added.

[stmt.return]

3 The copy-initialization of the result of the call is sequenced before the destruction of temporaries at the end of the full-expression established by the operand of the return statement, which, in turn, is sequenced before the destruction of local variables ([stmt.jump]) of the block enclosing the return statement.

The result object is initialized before that local variables in scope are destroyed. This means the throw is in the scope of the function. So, any exception thrown at this point is not on the caller side.

As such, marking the function as noexcept is gonna make the program terminate.

RVO doesn't change that. It only affects at what storage the result object is initialized in, but the initialization itself is still part of the function's execution.

like image 170
StoryTeller - Unslander Monica Avatar answered Oct 19 '22 01:10

StoryTeller - Unslander Monica


In this case, would the exception be propagated to the caller (i.e. it's considered to happen in main) and thus would be handled in the catch(...) handler?

I disagree. The copy must be done in the function scope as part of the return statement expression. Because local destructors are called only after return and they are definetely in the function scope.

Yes, C++17 made some guarantees about RVO, in particular this example is now guaranteed elision:

struct Foo{};

Foo bar(){
    Foo local;
    return Foo{};
    // Foo:~Foo(local);
}

Foo var = bar();

Still, if Foo:Foo() throws, the function is not noexcept. All RVO says that there is no move nor copy into var variable and Foo{} expression constructs the object at the location var. Yet it does not change when the object is constructed - in the function scope, before the destructors are even called.

Furthermore, mandatory RVO does not apply here since v is not a prvalue but l-value.

like image 45
Quimby Avatar answered Oct 19 '22 01:10

Quimby