KholdCode

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:

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.

Visualizing a Copy Constructor. Both the members and anything they refer to must be copied.

We’d like to perform value operations without unnecessary copies:

Vectorc=a+b;

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!

???operator+(Vectorconst&a,Vectorconst&b);

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:

Vectoroperator+(Vectorconst&a,Vectorconst&b){// create result of same sizeassert(a.size()==b.size());Vectorresult(a.size());// compute additionstd::transform(a.begin(),a.end(),// input 1b.begin(),// input 2result.begin(),// resultstd::plus<double>()// binary operation);returnresult;// ... but can we avoid the copy?}

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:

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.

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:

Vectora,b,c;// c is an lvalue. It is on the "left"// |c=a+b;// ^^^^^// the result of the "a + b" expression is an rvalue// It is on the "right"

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:

// some kind of factory functionVectorcreateVector(std::stringparam);VectormyVec=createVector("hello world");// ^ ^^^^^^^^^^^^^// | temporary std::string// |// temporary Vector//// There are two rvalues in the expression above.

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:

Visualizing a Move Constructor in two steps. The resources are 'stolen' from the source.

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:

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:

// Construct by moving subcomponents.// "origin" and "direction" refer to rvalues,// but are themselves lvalues in this scope.Ray::Ray(Vector&&origin,Vector&&direction)// move construct inner objects,// by casting inputs to rvalues:origin_(std::move(origin)),direction_(std::move(direction)){}

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 the Vector 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):

// How NOT to do itstd::string&&func()std::stringhello("Hello World!");returnstd::move(hello);}

To give some perspective, here’s the same function but with lvalues:

// Also, how not to do it.std::string&func()std::stringhello("Hello World!");returnhello;}

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:

Are moves free? Are moves faster than copies?

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.