There are many similar questions here. All of them asks about usage of std::move
in return
in specific cases. But I want to know when std::move
should be (or shouldn't be) used in return
statement in general.
Here I found the following answer:
All return values are already moved or else optimized out, so there is no need to explicitly move with return values.
Compilers are allowed to automatically move the return value (to optimize out the copy), and even optimize out the move!
So I expected that std::move
must never be used in return statement, because in any case compiler will optimize that. But I decided to check this and written the following test code:
class A
{
public:
A() {}
A(const A & a) { std::cout << "A(const A &)" << std::endl
A( A && a) { std::cout << "A( A &&)" << std::endl
}
A func1(bool first)
{
A a1, a2
return (first ? a1 : a2)
}
int main()
{
A a1(func1(true))
return 0
}
And here is what I got:
A(const A &)
So compiler did not automatically move the value in return statement, and I had to moved it manually:
return std::move(first ? a1 : a2)
This returned:
A( A &&)
However when I rewrote my code in such way:
A func2(bool first)
{
A a1, a2;
if (first) return a1; else return a2;
}
int main()
{
A a2(func2(true));
return 0;
}
I found that automatic move is working:
A( A &&)
Next test:
A func3(A &&a)
{
return a;
}
int main()
{
A a3(func3(A()));
return 0;
}
Result:
A(const A &)
So here return std::move(a)
must be used.
My understating is next. Compiler automatically moves (or even use RVO) only local variables (and parameters passed by value (they are actually local variables too)). Rvalue references passed as parameters must be moved manually (it's probably because compiler doesn't know whether this external reference will be used anywhere later). Also compiler use move only in “pure” return statement (return var;
) and can’t move when returning expression which uses local variables (return <some expression using var>
, e.g. return somefunc(var);
).
So std::move
should not be used in pure "return local variable" statements (using std::move
in this case prevents Return Value Optimization). In all other cases std::move
should be used in return
.
Is my understanding right?
Answers
My answer
When you return a local variable or parameter passed to your function by value, you don't need to use std::move
. It is even better not to use std::move
because without it compiler might use return value optimization which is even more efficient than moving.
In this case:
return (first ? a1 : a2)
you do not return a local variable, you return expression which result is first calculated by copying a1
or a2
, and then this result is returned (using return value optimization). That is why move does not automatically happen here.
In case you return a parameter passed by reference:
A func3(A &&a)
{
return a;
}
All named r-value references automatically become l-value references until you convert them back to r-value by std::move
. That is why you have to use std::move
. But people say that things were changed in C++20 where you don't have to use std::move
in such case.
If you're returning a local variable, don't use move()
. This will allow the compiler to use NRVO, and failing that, the compiler will still be allowed to perform a move (local variables become R-values within a return
statement). Using move()
in that context would simply inhibit NRVO and force the compiler to use a move (or a copy if move is unavailable). If you're returning something other than a local variable, NRVO isn't an option anyway and you should use move()
if (and only if) you intend to pilfer the object.
It's quite simple.
return buffer;
If you do this, then either NRVO will happen or it won't. If it doesn't happen then buffer
will be moved from.
return std::move( buffer );
If you do this, then NVRO will not happen, and buffer
will be moved from.
So there is nothing to gain by using std::move
here, and much to lose.
There is one exception* to the above rule:
Buffer read(Buffer&& buffer) {
return std::move( buffer );
}
If buffer
is an rvalue reference, then you should use std::move
.
This is because references are not eligible for NRVO, so without
std::move
it would result in a copy from an lvalue.
This is just an instance of the rule "always move
rvalue references
and forward
universal references", which takes precedence over the
rule "never move
a return value".
* As of C++20 this exception can be forgotten. Rvalue references in return
statements are implicitly moved from, now.