Return function and noexcept

This question is twofold "Constructor with parameter by value and noexcept" . This question showed that the lifecycle management of a function argument by value is handled by the calling function; therefore, the caller handles any exceptions that occur, and the called function can noexcept itself noexcept . I am wondering how the output end is handled using noexcept .

 MyType MyFunction( SomeType const &x ) noexcept; //... void MyCaller() { MyType test1 = MyFunction( RandomSomeType() ); MyType test2{ MyFunction( RandomSomeType() ) }; //... test1 = MyFunction( RandomSomeType() ); test2 = std::move( MyFunction(RandomSomeType()) ); MyFunction( RandomSomeType() ); // return value goes to oblivion } 

Let's say that the return value was successfully created in MyFunction . And let them say that the corresponding special member functions (copy / move-assign / construction) of MyType may not be noexcept .

  • Follow the rules RVO / NRVO / Whatever-from-C ++ 11 regarding the transfer of return values ​​from the called function to the caller, means that the transfer is always not executed, regardless of the noexcept state of the corresponding special member function?
  • If the answer to the previous question is “no,” then if the return value is passed, does the exception count against the called function or the caller?
  • If the answer to the previous question is “the function being called”, then the usual noexcept token on MyFunction will call std::terminate . How should the MyFunction noexcept profile be modified? When I asked about this on Usenet, the respondent thought it should be std::is_nothrow_move_assignable<MyType>::value . (Note that MyCaller used several methods for using the return value, but MyFunction does not know which one is used! The answer should cover all cases.) Does it matter if MyType changed to be copyable, but not movable?

So, if the worst cases of the second and third questions are accurate, then any function that returns by value cannot have a simple noexcept if the return type has a noexcept method! Types with moving movements should now be rare, but the template code still needs to "dirty" itself with is_nothrow_move_assignable every time a return-by-value is used.

I think the violation of the function of the called function is violated:

 MyType MyFunction( SomeType const &x ) noexcept( ??? ) { //... try { return SOME_EXPRESSION; // What happens if the creation of SOME_EXPRESSION succeeds, but the // move-assignment (or whatever) transferring the result fails? Is // this try/catch triggered? Or is there no place lexically this // function can block a throwing move!? } catch (...) { return MyType(); // Note that even if default-construction doesn't throw, the // move-assignment may throw (again)! Now what? } } 

This problem, at least for me, seems to be fixed at the end of the caller (just wrap the move-assignment with try / catch ), but not fixed from the end of the called function. I think the caller should handle this, even if we need to change the C ++ rules for this. Or at least some kind of defect report is required.

+4
source share
2 answers

To answer part of your question, you may ask if a particular type is constructive:

 #include <type_traits> MyType MyFunction( SomeType const &x ) noexcept(std::is_nothrow_move_constructible<MyType>::value) { // .... } 
+3
source

I think your question is confusing because you are talking about “transferring” from callee to caller, and that is not the term we use in C ++. The easiest way to think about the return values ​​of a function is that the called party communicates with the calling party through a “reverse slot” (a temporary object constructed by the called party and destroyed by the calling party). The responder is responsible for constructing the return value in the "return slot", and the caller is responsible for obtaining the value from the "return slot" (if desired), and then destroys everything that remains in the "return slot".

 MyType MyFunction(SomeType const &x) noexcept { return SOME_EXPRESSION; } void MyCaller() { MyType test1 = MyFunction( RandomSomeType() ); // A MyType test2{ MyFunction( RandomSomeType() ) }; // B //... test1 = MyFunction( RandomSomeType() ); // C test2 = std::move( MyFunction(RandomSomeType()) ); // D MyFunction( RandomSomeType() ); // E } 

First: return SOME_EXPRESSION; causes the result of SOME_EXPRESSION moved to the "reverse slot" of MyFunction . This step can be elided . If the move is not canceled, then MyType move-constructor will be called. If this move constructor throws an exception, you can catch the exception through the try block around return itself or through the try block function.

Case A : Here move-ctor inside MyFunction (which can be rejected), and then move-ctor in test1 (which can be canceled).

Case B : Same as case A

Case C : Here is the move-ctor inside MyFunction (which can be rejected), and then the move destination in test1 .

Case D : Same as case C Calling std::move doesn't do any good, and it's a bad style to write it.

Case E : There is a move-ctor inside MyFunction (which can be rejected) and what it is.

If exceptions are thrown during move-ctor or assignment of redirection, you can catch them by wrapping test1 code in try-block. The code inside MyFunction does not matter at this point; MyFunction does not know or care about what the caller will do with the returned object. Only the caller knows, and only the caller can catch exceptions thrown by the caller.

0
source

Source: https://habr.com/ru/post/1396307/


All Articles