Daily C++11: Rvalue references and move constructors

Probably one of the most important reasons that makes C programmers run away from C++ is the gross inefficiency of having temporaries. The problem was that the language encouraged unnecessary copies; someone who wanted to keep performance as a priority would have avoided code like:
[code language=”cpp”]#include <cstdio>
#include <vector>
using namespace std;

vector<int> getSequence (int start, int end)
{
vector<int>; result;
for (int i=start; i<=end; i++)
result.push_back (i);
return result;
}

int main ()
{
auto seq = getSequence (10, 20);
for (auto i : seq)
{
printf ("Value: %d\n", i);
}
return 0;
}[/code]
Here one would traditionally expect to have a vector created in the getSequence call, then that vector to be copied into the seq vector. Also, this would be a deep copy – which means that nothing gets reused from the temporary rvalue. Most of the times, the compiler is able to trick the code into not creating copies; however, in certain situations, such optimizations are not possible.

The answer in C++11 is to create move constructors. Such a move constructor allows the seq object from our example to steal the content of the result of getSequence, and no unnecessary copy to be performed. This gives new life to containers, for example, because now you can safely use standard containers  in conjunction with complex classes with less of a performance penalty.

One can declare an rvalue reference by using the &&. An rvalue reference is a reference to data that can be moved; this makes it clear that that reference is a temporary vessel for data. This is what is used to denominate a move constructor:
[code language=”cpp”]#include <cstdio>
#include <vector>
using namespace std;

class C {
public:
C(){puts ("Creation");}
C(const C&) {puts ("Copy was made");}
// C++11 only
C(C&&) {puts ("Move was made");}
};

int main ()
{
puts ("Hello");
vector<C> v;
v.push_back(C());
v.push_back(C());
v.insert(v.begin(), C());
return 0;
}[/code]
If the C++11 only line is removed, the code can be compiled the output of the code is desastrous:
[code]Hello
Creation
Copy was made
Creation
Copy was made
Copy was made
Creation
Copy was made
Copy was made
Copy was made[/code]
But just by adding the new move constructor, what we’ll see is completely different.
[code]Hello
Creation
Move was made
Creation
Move was made
Move was made
Creation
Move was made
Move was made
Move was made[/code]
This means that while only three objects are fully constructed, the rest are copied in the first case, and moved in the second case. This means that the equivalent of a shallow copy is done in the second case, and a full deep copy is made in the first case. This can be tremendously costly, especially if memory allocations need to be made.

One might say that one can use pointers to pass to the standard containers. But this is not always the most efficient: in our example, the class instances are created on the stack, which is many times faster than doing memory allocation.

If I was to name one performance improvement that would make you move from C++03 to C++11, this would be it. The standard containers suddenly have super-powers, and the performance improvements are through the roof.

Comments

Daily C++11: Rvalue references and move constructors — 2 Comments

  1. Yes, I saw this in the c++ presentation by Sutter you linked to a while back. It’s a really nice feature.

    • This was actually the moment when C++11 conquered me back 🙂 Incredible performance improvement, and I started trusting the standard library more. Later, when I’ll get to the library improvements, I think everyone will appreciate the new things added to the library. But now, I try to be thorough and write about most if not all of the new changes.
      I just realized I did not do an example which shows how such a move constructor should be implemented. I’ll get back to this. 🙂