11

If I have an object e of type Error which implements a move constructor, will throwing std::move( e ) use the move constructor of Error to "duplicate" e, so does it avoid making an actual copy of the object? So if I have

Error e;

throw std::move( e );

will the copy constructor of Error be called or not? This is of interest when your move constructor is noexcept (as it should be), but your copy constructor isn't.

sperber
  • 661
  • 6
  • 20
  • 1
    Just throw e and do not optimize exceptions (you might catch by const&, though). Also, the move is useless, here. –  Aug 27 '14 at 18:37
  • Not quite a dupe, but see http://stackoverflow.com/questions/3856445/can-someone-explain-rvalue-references-with-respect-to-exceptions – Sneftel Aug 27 '14 at 18:39
  • @DieterLücking: Why is the move useless. Will the move constructor be used first when duplicating e? As I understand it, a copy of e will be made before it is passed to a catch block (where you might catch it by reference). I am interested in the process of "making a copy" which is hidden from your eyes... – sperber Aug 27 '14 at 18:39
  • If you write `throw Error()` you get the same logical effect more concisely. – Cheers and hth. - Alf Aug 27 '14 at 18:43
  • 1
    @Cheersandhth.-Alf: Not in my (actual) case: In the context of my actual code, the specific exception object is given from the outside to the code block which throws it, so I can not create it in place as an rvalue object. – sperber Aug 27 '14 at 18:45

1 Answers1

11

§ 15.1 [except.throw]:

  1. Throwing an exception copy-initializes (8.5, 12.8) a temporary object, called the exception object. The temporary is an lvalue and is used to initialize the variable named in the matching handler.

  2. When the thrown object is a class object, the constructor selected for the copy-initialization and the destructor shall be accessible, even if the copy/move operation is elided (12.8).

§ 8.5 [dcl.init]:

  1. The initialization that occurs in the form

    T x = a;

, as well as in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and aggregate member initialization (8.5.1) is called copy-initialization. [ Note: Copy-initialization may invoke a move (12.8). —end note ]

§ 12.8 [class.copy]:

  1. When the criteria for elision of a copy operation are met or would be met save for the fact that the source object is a function parameter, and the object to be copied is designated by an lvalue, overload resolution to select the constructor for the copy is first performed as if the object were designated by an rvalue.

The aforementioned criteria for copy-elision include the following (§12.8 [class.copy]/p31):

  • in a throw-expression, when the operand is the name of a non-volatile automatic object (other than a function or catch-clause parameter) whose scope does not extend beyond the end of the innermost enclosing try-block (if there is one), the copy/move operation from the operand to the exception object (15.1) can be omitted by constructing the automatic object directly into the exception object

Copy-initialization of an exception may invoke a move-constructor to construct the actual exception object (even if std::move(e) is not explicitly invoked in a throw excpression), but not its matching handler (if tried to be caught by value).

Piotr Skotnicki
  • 46,953
  • 7
  • 118
  • 160
  • Thanks, but how is "may invoke a move constructor" to be understood: is it merely stated as a possible resolution, or does the move constructor have a definitive priority? Second, this means that the std::move( . ) in my code is not needed, right?! – sperber Aug 27 '14 at 20:35
  • 1
    @sperber: if the thrown exception object comes from outer scope of `try` block, then to me it looks `std::move(e)` is a must in your case. – Piotr Skotnicki Aug 28 '14 at 07:51
  • Can you make more precise what you mean by "outer scope of try block" in this context? Do you mean whenever I throw the object outside of ANY try block (which is the usual case)? If this was true, then it should really be the standard procedure in C++11 (you can't rely on compiler optimizations, but just on what the standard says) to enclose a throw in a std::move as a good implementation of an exception class should implement a move constructor and since copy-initializing an object by a move is in general more safe: the move constructor is most certainly noexcept. This I find odd. – sperber Aug 28 '14 at 11:55
  • 1
    @sperber: by saying *throwing object that comes from outer scope of try* I meant *throwing object declared in an outer scope than throw statement*, that is, throwing a variable `e`, that *does not* necessarily ends up its lifetime when `throw e;` is executed (e.g. an object that can be still used in outer scope(!) if that exception is caught). – Piotr Skotnicki Aug 28 '14 at 14:04
  • In other words: If e is an lvalue ;)?! Whether the move constructor is called or not should not be sensible to whether an lvalue originally comes from an lvalue of the same scope or not... – sperber Aug 28 '14 at 14:15
  • 1
    @sperber: No, neither lvalue, nor lvalue, nor xvalue. None of this. I'm talking about the compiler's optimization, so that when it knows the automatic (local) object will never-ever be accessed again, and would normally **also** end its lifetime **no matter** if `throw` is executed or not at all, only **then** it can be implicitly moved from rather than copied, when non explicitly casted to rvalue using `std::move(e)`, so using `throw e;` results in moving – Piotr Skotnicki Aug 28 '14 at 14:21
  • Ah ok, now I get you. But I am not interested in compiler optimizations at all. After all, you can't rely on it. You can only rely on what the standard says. And if the standard doesn't guarantee that the object will be moved (in the case discussed here), it means for me that you have to expect that it will be copied. So my question is: Given that you do not rely on compiler optimizations, then do you have to use std::move( . ) in any throw in any good designed c++11 code GIVEN that the object you throw is not an rvalue? It seems to be like that, and I find this very odd :)! – sperber Aug 28 '14 at 14:29
  • @sperber: you have always had RVO/copy elision as an example. Use `throw std::move(e);` if you want to make your intention clear. – Piotr Skotnicki Aug 28 '14 at 14:36
  • 1
    @sperber: Read §12.8.31 and §12.8.32. it is explained there in great detail. – Piotr Skotnicki Aug 28 '14 at 14:41
  • It seems that move constructor does not get called in MSVC++2013 when throwing an exception this way: http://stackoverflow.com/questions/32759433/move-constructor-is-not-called-when-throwing-an-exception – Serge Rogatch Sep 24 '15 at 10:49