Not sure to understand the advantage of the move constructor (or how it works or uses it)

I recently posted a SE question regarding the code below because it generated a compilation error. Someone was kind enough to answer this when you execute the move constructor or move the assignment operator, then the copy constructor is deleted by default. They also suggested that then I needed to use std::move() to get something like this:

 Image src(200, 200); Image cpy = std::move(src); 

Now this makes sense to me, because the fact that you want to use the move assignment operator or constructor move in this case should be explicit. src in this example is an lvalue, and nothing can tell the compiler how you really want to move its contents to cpy unless you explicitly express it with std::move . However, I have more problems with this code:

 Image cpy = src + src 

I did not put a copy for operator + below, but this is a simple type overload operator:

 Image operator + (const Image &img) const { Image tmp(std::min(w, img.w), std::min(h, img.h)); for (int j = 0; j < tmp.h; ++j) { for (int i = 0; i < tmp.w; ++i) { // accumulate the result of the two images } } return tmp; } 

In this particular case, I would suggest that the operator returns a temporary variable in the form of tmp and that the move assignment operator will be run in this case when you go to cpy = src + src . I am not sure what it is right to say that the result src + src is an lvalue value, because in fact what returns to tmp , but then tmp copied / assigned to cpy . Thus, before the move operator existed, this would invoke the default copy constructor. But why doesn't he use the move constructor in this case? It seems I also need to do:

 Image cpy = std::move(src + src); 

to make this work, which I assume gets the xvalue value for the variable returned by operator + of the Image class?

Can someone help me better understand this? and say i don't understand?

Thanks.

 #include <cstdlib> #include <cstdio> #include <cstring> #include <algorithm> #include <fstream> #include <cassert> class Image { public: Image() : w(512), h(512), d(NULL) { //printf("constructor default\n"); d = new float[w * h * 3]; memset(d, 0x0, sizeof(float) * w * h * 3); } Image(const unsigned int &_w, const unsigned int &_h) : w(_w), h(_h), d(NULL) { d = new float[w * h * 3]; memset(d, 0x0, sizeof(float) * w * h * 3); } // move constructor Image(Image &&img) : w(0), h(0), d(NULL) { w = img.w; h = img.h; d = img.d; img.d = NULL; img.w = img.h = 0; } // move assignment operator Image& operator = (Image &&img) { if (this != &img) { if (d != NULL) delete [] d; w = img.w, h = img.h; d = img.d; img.d = NULL; img.w = img.h = 0; } return *this; } //~Image() { if (d != NULL) delete [] d; } unsigned int w, h; float *d; }; int main(int argc, char **argv) { Image sample;// = readPPM("./lean.ppm"); Image res = sample; return 0; } 
+7
c ++ c ++ 11 move-semantics
source share
1 answer

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; // ... return 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."

+6
source share

All Articles