It seems I also need to do:
Image cpy = std::move(src + src);
Not in your case. IN
Image operator + (const Image &img) const { Image tmp;
You create and return an object of the same type as the return type of the function. This means that return tmp; will consider tmp as if it were an r value according to 12.8 / 32 (highlighted by me)
When the criteria for performing the copy operation are fulfilled or will be fulfilled, with the exception of the fact that the source object is a parameter of the function, and the object to be copied is indicated by the lvalue symbol, the overload resolution for selecting the constructor for the copy is first performed as if the object was designated rvalue .
The specified criteria are given in 12.8 / 31, in particular, in the first paragraph of the paragraph (underline):
- in the return statement in a function with a return type of the class, when the expression is the name of a non-volatile automatic object (different from the parameter of the function or the catch-clause parameter) with the same cv-unqualified type as the return type of the function, the copy / move operation can be omitted , building an automatic object directly into the return value of the function
Actually, a thorough reading of 12.8 / 31 says that in your case compilers are allowed (and the most popular ones) to omit the copy or even move around. This is called return value optimization (RVO). In fact, consider this simplified version of your code:
#include <cstdlib> #include <iostream> struct Image { Image() { } Image(const Image&) { std::cout << "copy\n"; } Image(Image&&) { std::cout << "move\n"; } Image operator +(const Image&) const { Image tmp; return tmp; } }; int main() { Image src; Image copy = src + src; }
Compiled with GCC 4.8.1, this code does not output, i.e. Copying a move operation is not performed.
Let's complicate the code a bit to see what happened when the RVO could not be executed.
Image operator +(const Image&) const { Image tmp1, tmp2; if (std::rand() % 2) return tmp1; return tmp2; }
Without large details, RVO cannot be used here not because the standard prohibits this, but for other technical reasons. When implementing operator +() code outputs move . That is, there is no copy, only the move operation.
The last word is based on Matthew M's answer to zoska in OP. As Matti M rightly said, it is not practical to do return std::move(tmp); because it prevents RVO. Indeed, with this implementation
Image operator +(const Image&) const { Image tmp; return std::move(tmp); }
The output of move , i.e. the move constructor is called, whereas, as we saw, with return tmp; the copy / move constructor is not called. This is the correct behavior because the expression return std::move(tmp) is not the name of a non-volatile automatic object, as required by the RVO rule above.
Refresh In response to user comment18490. The operator +() implementation, which introduces tmp and tmp2 , is more likely an artificial way to prevent RVO. Let's go back to the initial implementation and consider another way to prevent RVO, which also shows the big picture: compile the code with the -fno-elide-constructors option (also available in clang). Exit (in GCC, but it may differ in clang)
move move
When a function is called a stack, memory is allocated to create the returned object. I emphasize that this is not the tmp variable above. This is another unnamed temporary object.
Then return tmp; starts a copy or moves from tmp to an unnamed object, and initialization Image cpy = src + src; finally copy / move the nameless object to cpy . This is the basic semantics.
Regarding the first copy / move, we have the following. Since tmp is an lvalue, the copy constructor is usually used to copy from tmp to an unnamed object. However, the special offer above makes an exception and says that tmp in return tmp; should be considered as if it were an rvalue. Therefore, the move constructor is called. Furthermore, when RVO is performed, the movement is rejected and tmp actually created on top of the unnamed object.
As for the second copy / move, it is even simpler. An unnamed object is an rvalue, and so the move constructor is chosen to go from it to cpy . Now another optimization (similar to RVO, but AFAIK has no name), also specified in 12.8 / 31 (third point), which allows the compiler to avoid using an unnamed temporary and use cpy memory instead. Therefore, when RVO and this optimization are in place of tmp , the unnamed object and cpy are essentially "the same object."