std::move
is not really moving anything, but tells the compiler that a value can be considered as no longer useful. It is technically a cast to a RValue, and allows overload resolution to select the version of a function that will perform destructive operations on that value (therefore actually moving from it).
void f(A const &a); // Just reading from a void f(A&& a); // I can perform destructive operations on a, like resource stealing void g() { A a; f(a); // First overload is selected f(std::move(a)); // Second overload is selected }
As a consequence, calling std::move
on an object and then not directly using the returned value as a function argument is not the typical pattern, and may be indicative of a bug. Note that calling a member function on the result of std::move
is considered as passing it to a function (as the hidden this
parameter), as well as using it as an operand (the called function is the overloaded operator) or initializing an object with it (the called function is the constructor).
void sink(A &&a) {
std::move(a); // Noncompliant, and not doing anything
}
void f(A &&a) {
// May or may not move the member name, depending on its type, the intent is not clear anyways,
// for instance, is `a` supposed to be in a moved-from state after the call?
g(std::move(a).name); // Noncompliant
}
void f(A &&a) {
g(std::move(a.name)); // Compliant, `a.name` is in moved-from state, `a` itself is not
Even if calling a built-in operator or initializing data of built-in type are not function calls, for consistency with cases that involve user-defined types, this rule will not report in those cases:
struct Data {A a; int b; };
Data add(Data &&d1, Data &&d2) {
return Data{
std::move(d1.a) + std::move(d2.a), // Compliant, operator+ might have an overload for A&&
std::move(d1.b) + std::move(d2.b) // Compliant by exception
};
}