std::move is totally unnecessary when returning from a function, and really gets into the realm of you -- the programmer -- trying to babysit things that you should leave to the compiler.
std::move() is a cast that produces an rvalue-reference to an object, to enable moving from it.
std::move is actually just a request to move and if the type of the object has not a move constructor/assign-operator defined or generated the move operation will fall back to a copy.
"std::move" should only be used where moving can happenWhen passing the result of std::move as a const reference argument. In this case, no object will be moved since it's impossible to call the move constructor from within the function.
No. Whenever a local variable in a return
statement is eligible for copy elision, it binds to an rvalue reference, 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 correction.] 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 compile, because the move constructor must be considered first; you'd have to say something to the effect 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 &)
}
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 ]
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 ;-)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With