I was at a lecture with Bjarne Stoustrup recently, he was talking about c++ 11 and why it made sense.
One of his examples of new awesomeness was the news ‘&&’ symbol for move constructors.
Then I want home and started thinking, “When would 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 is supposed to solve. The n3 variable is constructed from a reference to the value returned from the ‘+’ operator.
Except this is the output from running the code:
Build Constructor on 1
Build Constructor on 2
Build Constructor on 3
3
RUN SUCCESSFUL
What the output shows is that the copy constructor never gets called — and this is with optimizations turned off. I’m having a hard time twisting the arm of the code enough to make it run the copy construtor. wrapping the result in a std::pair did the trick, but it kept me thinking.
Is the argument of move-constructors in operator arithmetic’s actually a failed argument?
Why is’nt my copy constructor called and why is it called in :
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 output:
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 the logic is and why the pair operator basically screws up the performance?
update:
I did another modification and found that if I replaced make_pair with the actual templated constructor of the pair pair<const Number, const Number> this reduced the number of times the copy constructor got 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
So it would apear using make_pair is harmfull?
Consider this simple C++ code:
After executing the second line, how many copies of the string exist? The answer is two: one stored in
value, and one stored inhold. That is fine… sometimes. There will often be times when you want to give someone a copy of a string while keeping it for yourself. But there are times you don’t want to do that too. For example:This will create a
std::stringtemporary, which will then be passed toStringHolder‘s constructor. The constructor will copy-construct its member. After the constructor completes, the temporary will be destroyed. At one point, we had two copies of the string, for no reason whatsoever.There was no point of having two copies of the string. What we wanted to do was move the
std::stringparameter into theStringHolder, so that there’s only ever one copy of the string.That’s where move construction comes in.
A
std::stringis basically just a wrapper around a pointer to an allocated array of characters, and a size containing the length of that array (and a capacity, but nevermind that now). If you have astd::string, and you want to move it into the other, then the new string must claim ownership of that allocated array of characters, and the old string must relinquish ownership. In C++03, you could do that with aswapoperation:This moves the contents of
oldStrintonewStrwithout any memory allocation.C++11’s move syntax provides two important features that
std::swapdoes not.First, move can happen implicitly (but only when it’s safe to do so). You must explicitly call
swapif you want swapping; moving can happen by writing natural code. For example, take ourStringHolderfrom before and make one change:How many copies of this string are ever created? The answer is… just one: the construction of the temporary. Because it is a temporary, C++11 is smart enough to know that it can move-construct anything being initialized by it. So it move-constructs the value parameter of the
StringHolderconstructor (or more likely elides the construction altogether). This moves the stored memory from the temporary intonewMember. So no copying takes place.After that, we invoke the move constructor explicitly when we construct
member. This again moves the allocated memory fromnewMembertomember.We only ever allocate a string once. That can be a big savings in performance.
Now, how does this relate to constructors of your own types? Well, consider this code:
This time, we now have a class with a copy and move constructor. How many copies of the string do we get?
Two. Of course it’s two. We have
oldHoldandnewHold, each with a copy of the string.But, if we did this:
Then there would again only ever be one copy of the string lying around.
That’s why movement is important. That’s why it matters: it reduces the number of copies of things you may need to have lying around.
Your copy constructor wasn’t called because it was elided. It’s doing return-value optimization. Turning off optimization isn’t going to help, because most compilers will elide anyway. There is no reason not to when elision is possible.
For function return values, movement is important in cases where elision is not possible.