Write `std::move()` only when you need to explicitly move an object to another scope
Reason
We move, rather than copy, to avoid duplication and for improved performance.
A move typically leaves behind an empty object (C.64), which can be surprising or even dangerous, so we try to avoid moving from lvalues (they might be accessed later).
Notes
Moving is done implicitly when the source is an rvalue (e.g., value in a return treatment or a function result), so don't pointlessly complicate code in those cases by writing move explicitly. Instead, write short functions that return values, and both the function's return and the caller's accepting of the return will be optimized naturally.
In general, following the guidelines in this document (including not making variables' scopes needlessly large, writing short functions that return values, returning local variables) help eliminate most need for explicit std::move.
Explicit move is needed to explicitly move an object to another scope, notably to pass it to a "sink" function and in the implementations of the move operations themselves (move constructor, move assignment operator) and swap operations.
Example, bad
void sink(X&& x); // sink takes ownership of x
void user()
{
X x;
// error: cannot bind an lvalue to a rvalue reference
sink(x);
// OK: sink takes the contents of x, x must now be assumed to be empty
sink(std::move(x));
// ...
// probably a mistake
use(x);
}
Usually, a std::move() is used as an argument to an && parameter. And after you do that, assume the object has been moved from (see C.64) and don't read its state again until you first set it to a new value.
void f()
{
string s1 = "supercalifragilisticexpialidocious";
string s2 = s1; // ok, takes a copy
assert(s1 == "supercalifragilisticexpialidocious"); // ok
// bad, if you want to keep using s1's value
string s3 = move(s1);
// bad, assert will likely fail, s1 likely changed
assert(s1 == "supercalifragilisticexpialidocious");
}
Example
void sink(unique_ptr<widget> p); // pass ownership of p to sink()
void f()
{
auto w = make_unique<widget>();
// ...
sink(std::move(w)); // ok, give to sink()
// ...
sink(w); // Error: unique_ptr is carefully designed so that you cannot copy it
}
Notes
std::move() is a cast to && in disguise; it doesn't itself move anything, but marks a named object as a candidate that can be moved from. The language already knows the common cases where objects can be moved from, especially when returning values from functions, so don't complicate code with redundant std::move()s.
Never write std::move() just because you've heard "it's more efficient." In general, don't believe claims of "efficiency" without data (???). In general, don't complicate your code without reason (??). Never write std::move() on a const object, it is silently transformed into a copy (see Item 23 in Meyers15)
Example, bad
vector<int> make_vector()
{
vector<int> result;
// ... load result with data
return std::move(result); // bad; just write "return result;"
}
Never write return move(local_variable);, because the language already knows the variable is a move candidate. Writing move in this code won't help, and can actually be detrimental because on some compilers it interferes with RVO (the return value optimization) by creating an additional reference alias to the local variable.
Example, bad
vector<int> v = std::move(make_vector()); // bad; the std::move is entirely redundant
Never write move on a returned value such as x = move(f()); where f returns by value. The language already knows that a returned value is a temporary object that can be moved from.
Example
void mover(X&& x)
{
call_something(std::move(x)); // ok
call_something(std::forward<X>(x)); // bad, don't std::forward an rvalue reference
call_something(x); // suspicious, why not std::move?
}
template<class T>
void forwarder(T&& t)
{
call_something(std::move(t)); // bad, don't std::move a forwarding reference
call_something(std::forward<T>(t)); // ok
call_something(t); // suspicious, why not std::forward?
}
Enforcement
- Flag use of
std::move(x)wherexis an rvalue or the language will already treat it as an rvalue, includingreturn std::move(local_variable);andstd::move(f())on a function that returns by value. - Flag functions taking an
S&¶meter if there is noconst S&overload to take care of lvalues. - Flag a
std::moved argument passed to a parameter, except when the parameter type is anX&&rvalue reference or the type is move-only and the parameter is passed by value. - Flag when
std::moveis applied to a forwarding reference (T&&whereTis a template parameter type). Usestd::forwardinstead. - Flag when
std::moveis applied to other than an rvalue reference to non-const. (More general case of the previous rule to cover the non-forwarding cases.) - Flag when
std::forwardis applied to an rvalue reference (X&&whereXis a non-template parameter type). Usestd::moveinstead. - Flag when
std::forwardis applied to other than a forwarding reference. (More general case of the previous rule to cover the non-moving cases.) - Flag when an object is potentially moved from and the next operation is a
constoperation; there should first be an intervening non-constoperation, ideally assignment, to first reset the object's value.