Which is why the move constructor in C ++ 11 makes sense?

I recently attended a lecture at Bjarne Stoustrup, he talked about C ++ 11 and why it made sense.

One of his examples of new awesomeness was the news "& &" character for move constructors.

Then I want to go home and started thinking: "When will I ever need such a thing?".

My first example was the code below:

class Number { private: int value; public: Number(const int value) : value(value){ cout << "Build Constructor on " << value << endl; } Number(const Number& orig) : value(orig.value){ cout << "Copy Constructor on " << value << endl; } virtual ~Number(){} int toInt() const{ return value; } friend const Number operator+(const Number& n0, const Number& n1); }; const Number operator+(const Number& n0, const Number& n1){ return Number(n0.value + n1.value); } int main(int argc, char** argv) { const Number n3 = (Number(2) + Number(1)); cout << n3.toInt() << endl; return 0; } 

This code does exactly what the move constructor should solve. The variable n3 is built from a reference to the value returned by the "+" operator.

Also, this is the result of running the code:

 Build Constructor on 1 Build Constructor on 2 Build Constructor on 3 3 RUN SUCCESSFUL 

As the result shows, the copy constructor is never called - and this is when optimizations are turned off. It's hard for me to twist the code arm enough to get the console to start. wrapping the result in std :: pair did the trick, but it got me thinking.

Is move-constructors in operator arithmetic actually an unsuccessful argument?

Why my copy constructor is not called and why it is called:

 using namespace std; class Number { private: int value; public: Number(const int value) : value(value){ cout << "Build Constructor on " << value << endl; } Number(const Number& orig) : value(orig.value){ cout << "Copy Constructor on " << value << endl; } virtual ~Number(){} int toInt() const{ return value; } friend const std::pair<const Number, const Number> operator+(const Number& n0, const Number& n1); }; const std::pair<const Number, const Number> operator+(const Number& n0, const Number& n1){ return make_pair(Number(n0.value + n1.value), n0); } int main(int argc, char** argv) { const Number n3 = (Number(2) + Number(1)).first; cout << n3.toInt() << endl; return 0; } 

With an exit:

 Build Constructor on 1 Build Constructor on 2 Copy Constructor on 2 Build Constructor on 3 Copy Constructor on 3 Copy Constructor on 2 Copy Constructor on 3 Copy Constructor on 2 Copy Constructor on 3 3 RUN SUCCESSFUL 

I would like to know what logic is and why does a pair operator basically screw up performance?

update:

I made another modification and found that if I replaced make_pair with the actual template constructor of the pair pair<const Number, const Number> , this would reduce the number of times the copy constructor fired:

 class Number { private: int value; public: Number(const int value) : value(value){ cout << "Build Constructor on " << value << endl; } Number(const Number& orig) : value(orig.value){ cout << "Copy Constructor on " << value << endl; } virtual ~Number(){} int toInt() const{ return value; } friend const std::pair<const Number, const Number> operator+(const Number& n0, const Number& n1); }; const std::pair<const Number, const Number> operator+(const Number& n0, const Number& n1){ return std::pair<const Number, const Number>(Number(n0.value + n1.value), n0); } int main(int argc, char** argv) { const Number n3 = (Number(2) + Number(1)).first; cout << n3.toInt() << endl; return 0; } 

output:

 Build Constructor on 1 Build Constructor on 2 Build Constructor on 3 Copy Constructor on 3 Copy Constructor on 2 Copy Constructor on 3 3 RUN SUCCESSFUL 

Does this mean that using make_pair is harmful?

+7
source share
2 answers

Consider this simple C ++ code:

 class StringHolder { std::string member; public: StringHolder(const std::string &newMember) : member(newMember) {} }; std::string value = "I am a string that will probably be heap-allocated."; StringHolder hold(value); 

After completing the second line, how many copies of the line exist? The answer is two: one is stored in value , and one is stored in hold . This is normal ... sometimes. Often there are times when you want to give someone a copy of a string, keeping it for yourself. But there are times when you also do not want this. For example:

 StringHolder hold("I am a string that will probably be heap-allocated."); 

This will create a temporary std::string element, which will then be passed to the StringHolder constructor. The constructor will copy-build your member. After the constructor completes, temporary information will be destroyed. At some point, we had two copies of the string, for no reason.

There was no point in having two copies of the string. We would like to move the std::string parameter to StringHolder , so that there will be only one copy of the string.

What happens when the structure moves.

A std::string is just a wrapper around a pointer to a selected array of characters and a size containing the length of this array (and capacity, but no matter what it is now). If you have std::string , and you want to move it to another, then the new line should require ownership of the allocated array of characters, and the old line should give up ownership. In C ++ 03, you can do this using the swap operation:

 std::string oldStr = "I am a string that will probably be heap-allocated."; std::string newStr; std::swap(newStr, oldStr); 

This moves the contents of oldStr to newStr without allocating memory.

The C ++ 11 relocation syntax provides two important functions that std::swap does not support.

First, moving can be done implicitly (but only when it's safe). You must explicitly call swap if you want to swap; movement can occur by writing natural code. For example, take our StringHolder from earlier and make one change:

 class StringHolder { std::string member; public: StringHolder(std::string newMember) : member(std::move(newMember)) {} }; StringHolder hold("I am a string that will probably be heap-allocated."); 

How many copies of this line have ever been created? The answer ... only one: the construction of a temporary. Since it is temporary, C ++ 11 is smart enough to know that it can move-build anything that it initializes. Therefore, it moves - it creates a value parameter of the StringHolder constructor (or, most likely, completely excludes the construction). This moves the stored memory from temporary to newMember . Therefore, copying does not occur.

After that, we explicitly call the move constructor when building member . This again moves the allocated memory from newMember to member .

We select a line only once. This can be a big performance savings.

Now, how does this apply to the constructors of your own types? Well, consider this code:

 class StringHolder { std::string member; public: StringHolder(std::string newMember) : member(std::move(newMember)) {} StringHolder(const StringHolder &old) : member(old.member) {} StringHolder(StringHolder &&old) : member(std::move(old.member)) {} }; StringHolder oldHold = std::string("I am a string that will probably be heap-allocated."); StringHolder newHold(oldHold); 

This time we now have a class with the constructor copy and move. How many copies of the string do we get?

Two. Of course, these are two. We have oldHold and newHold , each with a copy of the string.

But if we did this:

 StringHolder oldHold = std::string("I am a string that will probably be heap-allocated."); StringHolder newHold(std::move(oldHold)); 

Then again there will be only one copy of the line lying around.

That is why movement is important. That's why it matters: it reduces the number of copies of things you may need to keep them lying.


Why is this not my copy constructor called

Your copy constructor was not called because it was deleted. It does an optimization of the return values. Disabling optimization will not help, because most compilers will still refuse. There is no reason not to do this when elite is possible.

For return values โ€‹โ€‹of a function, motion is important in cases where an exception is not possible.

+13
source

This can help to understand the meaning of move semantics if you overload your operator+ this:

 Number operator+(Number&& n0, Number&& n1){ n0.value += n1.value; return std::move(n0); } 

This has two important changes:

  • returns a non-constant value
  • it takes rvalue arguments and modifies one of them instead of creating a new object

This allows your example to avoid one of the Build Constructor calls.

 Build Constructor on 1 Build Constructor on 2 Copy Constructor on 3 3 

Now the code creates two new objects and copies one, instead of creating three new ones, which is not a big advantage. But if you add a move constructor that can be used instead of a copy:

 Number(Number&& orig) : value(orig.value){ cout << "Move Constructor on " << value << endl; } Build Constructor on 1 Build Constructor on 2 Move Constructor on 3 3 

If a class allocates memory in the "Build" and "Copy" constructors, you have reduced the total number of allocations from three in the source code to two (provided that the move constructor does not allocate anything, but takes control of the memory belonging to the object from which it moves.)

Now, if you change the calculation to:

 Number n3 = Number(2) + Number(1) + Number(0); 

And compare the source code with the motion-enabled version, you should see the number of โ€œdistributionsโ€ reduced from five to three. The more time is involved, the greater the benefit of changing and moving from temporary, rather than creating new objects. The advantage is not only in avoiding copies, but also in not creating new resources for new objects, rather than taking ownership of existing objects.

+2
source

All Articles