Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does std::move prevent RVO?

In many cases when returning a local from a function, RVO (return value optimization) kicks in. However, I thought that explicitly using std::move would at least enforce moving when RVO does not happen, but that RVO is still applied when possible. However, it seems that this is not the case.

#include "iostream"  class HeavyWeight { public:     HeavyWeight()     {         std::cout << "ctor" << std::endl;     }      HeavyWeight(const HeavyWeight& other)     {         std::cout << "copy" << std::endl;     }      HeavyWeight(HeavyWeight&& other)     {         std::cout << "move" << std::endl;     } };  HeavyWeight MakeHeavy() {     HeavyWeight heavy;     return heavy; }  int main() {     auto heavy = MakeHeavy();     return 0; } 

I tested this code with VC++11 and GCC 4.71, debug and release (-O2) config. The copy ctor is never called. The move ctor is only called by VC++11 in debug config. Actually, everything seems to be fine with these compilers in particular, but to my knowledge, RVO is optional.

However, if I explicitly use move:

HeavyWeight MakeHeavy() {     HeavyWeight heavy;     return std::move(heavy); } 

the move ctor is always called. So trying to make it "safe" makes it worse.

My questions are:

  • Why does std::move prevent RVO?
  • When is it better to "hope for the best" and rely on RVO, and when should I explicitly use std::move? Or, in other words, how can I let the compiler optimization do its work and still enforce move if RVO is not applied?
like image 452
cdoubleplusgood Avatar asked Oct 09 '13 09:10

cdoubleplusgood


People also ask

What happens when you to std :: move?

std::move is used to indicate that an object t may be "moved from", i.e. allowing the efficient transfer of resources from t to another object. In particular, std::move produces an xvalue expression that identifies its argument t . It is exactly equivalent to a static_cast to an rvalue reference type.

Why is std :: move necessary?

std::move itself does "nothing" - it has zero side effects. It just signals to the compiler that the programmer doesn't care what happens to that object any more. i.e. it gives permission to other parts of the software to move from the object, but it doesn't require that it be moved.

Do I need to return std :: move?

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.

Can you std :: move a reference?

This can happen: When 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. std::move should only be used when the argument is passed by value or by r-value reference.


1 Answers

The cases where copy and move elision is allowed is found in section 12.8 §31 of the Standard (version N3690):

When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the constructor selected for the copy/move operation and/or the destructor for the object have side effects. In such cases, the implementation treats the source and target of the omitted copy/move operation as simply two different ways of referring to the same object, and the destruction of that object occurs at the later of the times when the two objects would have been destroyed without the optimization. This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):

  • in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv-unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value
  • [...]
  • when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move
  • [...]

(The two cases I left out refer to the case of throwing and catching exception objects which I consider less important for optimization.)

Hence in a return statement copy elision can only occur, if the expression is the name of a local variable. If you write std::move(var), then it is not the name of a variable anymore. Therefore the compiler cannot elide the move, if it should conform to the standard.

Stephan T. Lavavej talked about this at Going Native 2013 (Alternative source) and explained exactly your situation and why to avoid std::move() here. Start watching at minute 38:04. Basically, when returning a local variable of the return type then it is usually treated as an rvalue hence enabling move by default.

like image 79
Ralph Tandetzky Avatar answered Sep 21 '22 21:09

Ralph Tandetzky