About this blog

Save duplicate questions from disappearing from Google

In which cases should "std::move" be used in "return" statements and in which shouldn't?

Question

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.

Answer #1

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.

Answer #2

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.