In this article, I will try to explain move semantics in C++11 using a more pragmatic approach, by answering specific questions developers may ask. We’ll start with why moves are needed in the first place, then see how to use them, and eventually move onto common misconceptions and pitfalls.
Index
The article is quite large, so I included a short summary with links for each of the sections below:
-
- Moves make it possible to return a value out of scope directly, without copying
-
- Even in C++03, returning by value usually results in no copies because of copy elision.
-
Why do I really need moves and C++11?
- Transferring values into a scope.
- Moves allow value semantics, without extraneous copies.
-
What are rvalues and how do they relate to move semantics?
- Rvalues are expiring/temporary values.
- Rvalues cannot be used directly, except through rvalue references
&&
. - Rvalue references make it possible to specify a function overload for an expiring value.
-
-
Define a Move Constructor that performs 2 steps:
- Shallow Copy
- Nullify Source
-
-
How is
std::move
used? Does it perform the move?std::move
does not perform the move.- It is nothing more than a cast from an lvalue to an rvalue, to allow an actual move to happen (e.g. by causing an appropriate move constructor to be called)
-
Why do I need to use
std::move
on rvalue references?- rvalue references are themselves lvalues.
- To move an rvalue down through successive layers,
std::move
has to be applied each time, even to rvalue references.
-
Is there a difference between an rvalue and an rvalue reference?
- rvalue references refer to rvalues.
- A value returned from a function is already an rvalue.
- Returning an rvalue reference to a local is as bad as returning an lvalue reference to it.
-
Are moves free? Are moves faster than copies?
- They are not free: they still have to perform a shallow copy.
- For simple structures with no indirection, moves are copies.
-
- Expression Templates
- Faking Rvalues
Why are moves needed?
Let’s consider a simple fixed-size vector of numbers:
Even though these vectors have a fixed size throughout their lifetime, they may be quite large, so copying them would be prohibitively expensive. The whole underlying array must be replicated.
We’d like to perform value operations without unnecessary copies:
Of course that leaves us with the problem of defining operator +
. What do we
return? Clearly we want to return a value if we want operations to compose, but
that could mean copying a massive vector!
Our instincts say:
- Returning by value is bad! We make a copy!
-
If we allocate on the heap and return a pointer, who deletes it?
- It doesn’t compose! Can’t chain several additions since we’re not working with values.
We need some way to move a value out of a scope directly, without copying
Copying the value out of a function and then deleting the local seems absurd. Let’s see what already exists in C++03 to tackle this problem.
Can I “move” in C++03?
Ideally, given the problem of implementing operator +
for two vectors, we’d
want to write this:
And that’s exactly what you should write, because the compiler will get rid of the copy for you! This happens because of copy elision (specified in the C++ standard in clause 12.8.31), and more specifically because of NRVO (Named Return Value Optimization). Returning a local variable by value is detected by the compiler, and the needless copy is elided. This optimization was first developed in 1991, so you can rest assured your compiler supports it.
Even in C++03, returning by value usually results in no copies because of copy elision.
Now, I say usually, because there are corner cases where this optimization will not trigger. See the Wikipedia Article for an overview, and consult your favourite compiler manual for more details.
In the next section we’ll consider another problem where move semantics are needed.
Why do I really need moves and C++11?
We saw that our previous problem of transferring values out of a terminating scope is still solvable in C++03, through clever compiler optimization. We need to cover the converse situation of transferring values into a scope, in situations like:
- Passing subcomponents to a constructor of an aggregate
- Handing off a value to another task or thread.
- Transferring ownership of a unique resource (e.g. a file handle, thread,
std::unique_ptr
)
In all of the cases above, there is a need to transfer without copying. Yet again, pointers are not the answer here. Using pointers for this transfer requires managing the storage (either the heap, or some shared memory), which should be unnecessary, and might even defeat the purpose entirely.
Simply put, transferring values in and out of a scope allows efficient passing of values.
Moves allow value semantics, without extraneous copies.
To keep a concrete problem in mind, let’s look at initializing an aggregate
object from two Vector
objects. We’ll define Ray
concretely soon.
Now that we have defined the problem, how do we solve it?
What are rvalues and how do they relate to move semantics?
The key feature that we need, is to tell apart regular values, and “temporary” ones. Once we know that, we can decide whether to copy or move. By looking at a typical assignment we can see where the terms lvalue and rvalue come from:
- Lvalues are values that you can freely refer to in their scope because they are bound to a name- such as variables. As such, lvalues can be assigned to, and used several times in a single scope.
- Rvalues, cannot be directly referred to because they are temporaries and are not bound to a name- such as values returned by a function, or the direct result of an inline construction:
Given that by definition we cannot refer directly to rvalues, we need
rvalue references (&&
) to be able to bind to them. This allows overloading
functions/methods/constructors for rvalue arguments which we know will expire.
Rvalues are values that are either temporary or on the verge of expiry. They cannot be used directly, except through rvalue references
&&
. Rvalue references make it possible to specify a function overload for an expiring value, such as a move constructor.
How is a move performed?
To do a move, we need a cheap way of transferring data from an expiring value.
By providing an overloaded constructor that takes an rvalue reference (&&
),
we can do that. Here is a Move Constructor for our Vector
that is cheaper
than a copy:
A Move Constructor involves:
-
A shallow copy
- We take pointers/resources directly from the other object
-
Nullify the source
- Having stolen the pointers/resources, we nullify these fields in the source so the destructor doesn’t release them.
How is std::move
used? Does it perform the move?
This article is about moves, but we have yet to use std::move
. What gives?
Unfortunately, std::move
is in some sense a misnomer. Think of std::move
as
a new type of cast that casts any expression to an rvalue. This cast makes
the compiler select the rvalue reference overload (for example a move
constructor), where the actual move is performed.
To be more concrete, and work towards a problem established earlier, let’s
define the Ray
class that has overloaded constructors:
Using std::move
we can tell the compiler that the value is no longer needed
in this scope. Solving the problem is now within reach. Given local lvalues
that we need to move, we can cast them to rvalues using std::move
:
It bears reiterating:
std::move
does not perform the move. It is nothing more than a cast from an lvalue to an rvalue, to allow an actual move to happen (e.g. by causing an appropriate move constructor to be called).
Why do I need to use std::move
on rvalue references?
We tackled moving lvalues down a layer, using std::move. What happens when we already have an rvalue reference that we need to move down another layer?
Let’s look at a concrete example using our Ray
class, and its constructor
that takes two Vectors
by rvalue reference. When composing a larger
aggregate out of smaller movable objects, we can delegate the process of moving
to the sub-components using std::move
:
To move down an rvalue through successive layers,
std::move
has to be applied each time, even to rvalue references.
Why is that? A mind-bending realization is that because rvalue references are bound to a name, they are themselves lvalues. We need to tell the compiler that this value is no longer needed in this scope. Stop here, and take a breath.
Let’s look at that code again, now with more annotations:
There are several important things to notice here:
- The constructor overload is chosen when both inputs are rvalues.
-
Once the rvalue references are bound, we can refer to them in the scope.
- This means that these rvalue references are lvalues!
- We have to use
std::move
to cast these references back to rvalues to pass them to the move constructors of theVector
objects.
Phew!
rvalue references bound to a name, can be referred to in their scope and are therefore lvalues.
If this last quote makes your head spin, just remember that to move a value
down through successive layers, std::move
has to be applied each time.
Is there a difference between an rvalue and an rvalue reference?
Yes! The two notions are very different. This misconception comes up time and time again, but not in the form of this direct question. I noticed that while learning the new concept of rvalues, the ideas of rvalues and rvalue references can become conflated together. Many refer to one or the other interchangeably. This is a stage I experienced, and was surprised that this is rarely disambiguated directly.
Rvalues and rvalue references are very different notions. Rvalues are the actual expiring values that cannot be used directly. To access rvalues, we use rvalue references.
The misconception often manifests itself in code like this (which doesn’t work):
To give some perspective, here’s the same function but with lvalues:
We all know that returning a reference to a local value is bad, since it goes out of scope- and that’s exactly what happens in both cases. Both references returned would be referring to already-destroyed objects.
A value returned from a function is already an rvalue. Returning an rvalue reference to a local is as bad as returning an lvalue reference to it.
Just so there is no confusion, here’s the simplest and correct way to do it:
Any temporaries and copies disappear due to copy elision.
Are moves free? Are moves faster than copies?
Are moves free? No. As we have seen:
- Moves are shallow copies
- That also nullify the source of the copy
Both operations have to be performed to move an object. It will never be as fast as not doing anything.
Are moves faster than copies? This depends. Moves are much faster for structures with indirection, (like vectors), since copying all of the data is not required (just pointers). However, moves are copies for structures without pointers or indirection, since we still need to transfer all the member variables. If there is no “depth” to the structure, then there are no performance benefits to moves.
For simple structures with no indirection, moves are copies.
Clever solutions in C++03
There are other clever ways of getting rid of unnecessary copies and operations from C++03 code. Both go beyond the scope of this article, but you can read up on these techniques at the links below:
-
Expression Templates
- Expression templates allow encoding a tree of expressions (e.g. mathematical expressions) and performing simplifications and optimizations on them, all at compile time.
- The implementations are very complex, but the user API ends up being extremely intuitive, while performing extremely efficiently.
- A great library that uses this concept heavily is Eigen.
-
Faking Rvalues
- Boost Move emulates rvalues and move semantics through very clever C++ tricks, allowing the creation of movable objects in C++03.
Conclusion and other resources
I hope this has helped some of you understand the basics of move semantics. Please comment below with corrections/suggestions, and if I have missed any other common questions about moves in C++11/14.
To dive deeper into move semantics and various pitfalls, here are a few more resources to look at:
-
Dave Abrahams – Want Speed? Pass by Value
-
Scott Meyers – An Effective C++11/14 Sampler
- This a great and dense talk on best practices and gotchas in C++11/14, that begins with advice on move semantics.
-
Scott Meyers – Universal References in C++11
- An indepth talk and article on perfect forwarding.
-
Thomas Becker – C++ Rvalue References Explained
- A thorough article focusing on rvalue references, and how they relate to moves, and perfect forwarding.
This article has been featured on isocpp.org and reddit.