Using std::move() when returning a value from a function to avoid to copy
Asked Answered
T

3

65

Consider a type T supporting the default move semantics. Also consider the function below:

T f() {
   T t;
   return t;
}

T o = f();

In the old C++03, some non-optimal compilers might call the copy constructor twice, one for the "return object" and one for o.

In c++11, since t inside f() is an lvalue, those compilers might call the copy constructor one time as before, and then call the move constructor for o.

Is it correct to state that the only way to avoid the first "extra copy" is to move t when returning?

T f() {
   T t;
   return std::move(t);
}
Tallia answered 5/8, 2012 at 16:0 Comment(2)
here is a similar questionPrecaution
related FAQSanitary
P
63

No. Whenever a local variable in a return statement is eligible for copy elision, it binds to an rvalue re­fe­rence, and thus return t; is identical to return std::move(t); in your example with respect to which constructors are eligible.

Note however that return std::move(t); prevents the compiler from exercising copy elision, while return t; does not, and thus the latter is the preferred style. [Thanks to @Johannes for the cor­rect­ion.] If copy elision happens, the question of whether or not move construction is used becomes a moot point.

See 12.8(31, 32) in the standard.

Note also that if T has an accessible copy- but a deleted move-constructor, then return t; will not com­pile, because the move constructor must be considered first; you'd have to say something to the ef­fect of return static_cast<T&>(t); to make it work:

T f()
{
    T t;
    return t;                 // most likely elided entirely
    return std::move(t);      // uses T::T(T &&) if defined; error if deleted or inaccessible
    return static_cast<T&>(t) // uses T::T(T const &)
}
Premillenarian answered 5/8, 2012 at 16:4 Comment(4)
Thanks. What about the number of calls to the move constructor? Can it be two as the number of call to copy constructors with some c++03-complaiant compilers?Tallia
It is not identical. It is only identical with regard to whether the move constructor can be called. If you write return std::move(t); the move constructor must be called if the compiler doesn't know what it does. If you write return t; the move constructor call can be elided even if it could have side effects.Chronic
Also good to note this only applies to the returned value itself, not in a case like this: return std::make_pair(a, b);, here a and b should be explicitly moved, if needed.Weswesa
@Weswesa Thank you for the note. Could you (someone) please elaborate how this should look like for pair, or tuple ? What if I make the pair in the line above and then return it in the next ? This can't be any difference :-o What do I get wrong ?Oberg
W
19

No. The best practice is directly return t;.

In case class T has move constructor not deleted, and notice t is a local variable that return t is eligible for copy elision, it move constructs the returned object just like return std::move(t); does. However return t; is still eligible to copy/move elision, so the construction may be omitted, while return std::move(t) always constructs the return value using move constructor.

In case move constructor in class T is deleted but copy constructor available, return std::move(t); will not compile, while return t; still compiles using copy constructor. Unlike @Kerrek mentioned, t is not bound to an rvalue reference. There's a two-stage overload resolution for return values that eligible for copy elision -- try move first, then copy, and both move and copy is possibly elided.

class T
{
public:
    T () = default;
    T (T&& t) = delete;
    T (const T& t) = default;
};

T foo()
{
    T t;
    return t;                   // OK: copied, possibly elided
    return std::move(t);        // error: move constructor deleted
    return static_cast<T&>(t);  // OK: copied, never elided
}

If the return expression is lvalue and not eligible for copy elision (most likely you are returning a non-local variable or lvalue expression) and you still would like to avoid copy, std::move will be useful. But keep in mind that the best practice is make copy elision possible to happen.

class T
{
 public:
    T () = default;
    T (T&& t) = default;
    T (const T& t) = default;
};

T bar(bool k)
{
    T a, b;
    return k ? a : b;            // lvalue expression, copied
    return std::move(k ? a : b); // moved
    if (k)
        return a;                // moved, and possibly elided
    else
        return b;                // moved, and possibly elided
}

12.8(32) in the standard describes the process.

12.8 [class.copy]

32 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. If overload resolution fails, or if the type of the first parameter of the selected constructor is not an rvalue reference to the object’s type (possibly cv-qualified), overload resolution is performed again, considering the object as an lvalue. [ Note: This two-stage overload resolution must be performed regardless of whether copy elision will occur. It determines the constructor to be called if elision is not performed, and the selected constructor must be accessible even if the call is elided. —end note ]

Whyte answered 31/10, 2013 at 4:31 Comment(1)
"In case move constructor in class T is deleted but copy constructor available, … return t; still compiles using copy constructor" — this seems to be not true.Holly
K
3

Ok, I would like to drop a comment on this. This question (and the answer) made me believe that it is not necessary to specify std::move on the return statement. However I was just thought a different lesson while dealing with my code.

So, I have a function (it's actually a specialization) that takes a temporary and just returns it. (The general function template does other stuff, but the specialization does the identity operation).

template<>
struct CreateLeaf< A >
{
  typedef A Leaf_t;
  inline static
  Leaf_t make( A &&a) { 
    return a;
  }
};

Now, this version calls the copy constructor of A upon returning. If I change the return statement to

Leaf_t make( A &&a) { 
  return std::move(a);
}

Then the move constructor of A gets called and I can do some optimizations there.

It might not be 100% matching your question. But it is false to think that return std::move(..) is never necessary. I used to think so. Not any more ;-)

Kentledge answered 5/11, 2012 at 13:33 Comment(1)
This is different from the original question. The original question was about return x, where x is a local variable. When x is a local variable, return x is better because the compiler will treat x as an rvalue within the return because it knows x is a local. When x is a reference, the compiler will not give it special treatment. And since the type of the "a" variable in your example is "A&", you need to use move to change it to "A&&".Kimberleekimberley

© 2022 - 2024 — McMap. All rights reserved.