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:
std::move
prevent RVO?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?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.
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.
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.
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.
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
(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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With