Move semantics - what it's all about?
Forget about C++0x for the moment. Move semantics are something that is language independent -- C++0x merely provides a standard way to perform operations with move semantics.
Definition
Move semantics define the behaviour of certain operations. Most of the time they are contrasted with copy semantics, so it would be useful to define them first.
Assignment with copy semantics has the following behaviour:
// Copy semantics
assert(b == c);
a = b;
assert(a == b && b == c);
i.e. a
ends up equal to b
, and we leave b
unchanged.
Assignment with move semantics has weaker post conditions:
// Move semantics
assert(b == c);
move(a, b); // not C++0x
assert(a == c);
Note that there is no longer any guarantee that b
remains unchanged after the assignment with move semantics. This is the crucial difference.
Uses
One benefit of move semantics is that it allows optimisations in certain situations. Consider the following regular value type:
struct A { T* x; };
Assume also that we define two objects of type A
to be equal iff their x
member point to equal values.
bool operator==(const A& lhs, const A& rhs) { return *lhs.x == *rhs.x; }
Finally assume that we define an object A
to have sole ownership over the pointee of their x
member.
A::~A() { delete x; }
A::A(const A& rhs) : x(new T(rhs.x)) {}
A& A::operator=(const A& rhs) { if (this != &rhs) *x = *rhs.x; }
Now suppose we want to define a function to swap two A
objects.
We could do it the normal way with copy semantics.
void swap(A& a, A& b)
{
A t = a;
a = b;
b = t;
}
However, this is unnecessarily inefficient. What are we doing?
- We create a copy of
a
intot
. - We then copy
b
intoa
. - Then copy
t
intob
. - Finally, destroy
t
.
If T
objects are expensive to copy then this is wasteful. If I asked you to swap two files on your computer, you wouldn't create a third file then copy and paste the file contents around before destroying your temporary file, would you? No, you'd move one file away, move the second into the first position, then finally move the first file back into the second. No need to copy data.
In our case, it's easy to move around objects of type A
:
// Not C++0x
void move(A& lhs, A& rhs)
{
lhs.x = rhs.x;
rhs.x = nullptr;
}
We simply move rhs
's pointer into lhs
and then relinquish rhs
ownership of that pointer (by setting it to null). This should illuminate why the weaker post condition of move semantics allows optimisations.
With this new move operation defined, we can define an optimised swap:
void swap(A& a, A& b)
{
A t;
move(t, a);
move(a, b);
move(b, t);
}
Another advantage of move semantics is that it allows you to move around objects that are unable to be copied. A prime example of this is std::auto_ptr
.
C++0x
C++0x allows move semantics through its rvalue reference feature. Specifically, operations of the kind:
a = b;
Have move semantics when b
is an rvalue reference (spelt T&&
), otherwise they have copy semantics. You can force move semantics by using the std::move
function (different from the move
I defined earlier) when b
is not an rvalue reference:
a = std::move(b);
std::move
is a simple function that essentially casts its argument to an rvalue reference. Note that the results of expressions (such as a function call) are automatically rvalue references, so you can exploit move semantics in those cases without changing your code.
To define move optimisations, you need to define a move constructor and move assignment operator:
T::T(T&&);
T& operator=(T&&);
As these operations have move semantics, you are free to modify the arguments passed in (provided you leave the object in a destructible state).
Conclusion
That's essentially all there is to it. Note that rvalue references are also used to allow perfect forwarding in C++0x (due to the specifically crafted type system interactions between rvalue references and other types), but this isn't really related to move semantics, so I haven't discussed it here.
Basically, rvalue references allow you to detect when objects are temporaries and you don't have to preserve their internal state. This allows for much more efficient code where C++03 used to have to copy all the time, in C++0x you can keep re-using the same resources. In addition, rvalue references enable perfect forwarding.
Have a look at this answer.
I read a ton of text explanations for about a year and didn't grasp everything about r-value references until I watch this excellent presentation by Scott Meyer : http://skillsmatter.com/podcast/home/move-semanticsperfect-forwarding-and-rvalue-references
He explain in a way that is funny and slow enough to understand each thing that happens in the processes.
I know, it 1h30 but really, it's the best explanation I've had in the last year.
After having read the articles (like the other answers), watching this video did melt it together in my mind in a consistent way and few days after I was able to explain it to some colleagues and explain how to use std::unique_ptr (as it is related - it only allow move semantics, not copy) because it requires understanding of std::move(), that requires understanding move semantics.