While moving my hosting I noticed some posts that I never bothered tidying up to post. And although there has been a bit of a break, I am still writing these introduction to C++11 posts. Chances are low that I will have the C++11 posts finished before C++14 arrives! Moving on…
Consider the following code snippet:
template
T t(a); // #1
a = b; // #2
b = t; // #3
}
On line #1, we end up with two copies of a, line #2 gives two copies of b and line three is back to two copies of (the original) a. If T is a type that requires extensive copying this requires quite a bit of overhead. The standard library has specialized cases for std::vector and std::string to remove that overhead, but it would be good to get rid of it in general.
Lets consider another example:
T foo() {
T t;
// do "foo" on t...
return t;
}
T u = foo();
Here foo() returns a temporary copy of t constructed on the stack and then that is passed to the T copy constructor to initialize u. Again this can have a substantial overhead. (I am ignoring return-type optimization here…)
From these examples, it should be clear that such overhead is reasonably common in C++ (and seems to be one of the biggest criticisms of the language). To fix this, a new non-const reference type is added in C++11, called an r-value reference and denoted T&&.
Firstly, what is an r-value? They are temporary objects that tend to fall on the right hand side of an equation. Because they are temporary objects, you can not assign to them. Also, you can not assign a r-value to a regular reference. E.g.
int& foo = bar()
will fail because bar() is returning a temporary value, which would be bad if it was able to be modified.
Using the r-value reference, we can add what is termed a “move constructor” and a “move assignment operator” to our class:
Foo::Foo(Foo&& f);
Foo& Foo::operator=(Foo&& f);
Note their arguments are both non-const, the reasons for which should become obvious.
Lets look at the copy constructor some more. For the sake of this example I will assume that Foo only contains an array and a variable storing its size. The copy constructor would look something like:
Foo::Foo(Foo&& f) {
size=f.size;
array=f.array;
f.size=0;
f.array=NULL;
}
What the move constructor has done is pilfered the resources of the temporary object, then reset the temporary object’s resources to being empty. This saves overhead of copying the array and setting the NULL prevents the memory being freed when the temporary object goes out of scope. The move assignment operator is exactly the same, except it frees any resources it currently owns first (like a normal assignment operator does).
How does this help our situation in the swap function above? We tell the compiler that it is OK to treat the right hand side of all the assignments as r-values using the std::move
template
T t(move(a));
a = move(b);
b = move(t);
}
Now at any time there is only one copy of a and b and the copying does not require any storage overhead.