Introduction

In this article, I will discuss the move functionality in the C++11, particular emphasis will be given to writing move constructors and move assignment operators, to issues with perfect forwarding. Some of the features are well presented in [1,2], and I recommend readers to view the presentation or read the notes. The document, which is close to C++11 Standard, can be found here [3].

Background

The move functionality has been designed to optimize data transferring between the objects, in situations when objects have large contents allocated on the heap. Such an object can be represented as comprising of two parts: a small shell (the class itself) and its heap-allocated contents (HAC), which is pointed to from the shell. Say, we want two copy the contents of object B to A. In most cases sizes of their contents will be different. The objects are shown here:

The usual operation will be to delete the contents of A:

Then, it will be necessary to allocate the new space, which has the same size as the contents of B:

And then it is required to copy the contents of B to A:

Normally this is how it should be done if we need to use both objects A and B in the future. But if, for some reason, the object B is not needed (such reasons we will be discussing shortly), it is possible to swap the contents of A and B:

There are two major advantages of the last approach:

(1) we do not have to allocate new memory from the heap, which reduces the time and memory space;

(2) we do not have to copy the contents of B to A, which saves the processor time on copying.

Obviously, the larger the contents the bigger the gain.

The RValue References

In order to identify the objects that are good for moving, a new feature is introduced in C++11, which is called the rvalue reference. A rvalue reference is usually used to specify a reference to an object that is going to be destroyed. In C++11, the symbol && (two ampersand characters) is put after the name of a type to identify that it is an rvalue reference. For instance, the rvalue reference to class T will be written as: T&&.

The main use of rvalue references is to specify formal parameters of functions. If, in the past, for parameters (of the class T), we used two main specifications:

(1) T&

(2) const T&

The first has been used to specify constants and values which are going to be changed, the second to specify constants and values, which are not going to be changed.

Now, in C++11, there are three options:

(1) const T&

(2) T&

(3) T&&

First of all we can still use only two options. But to make your code more efficient, it is good to take advantage of the third option. Obviously, if the third option is used, the area covering the first two is reduced.

The third option refers to rvalue refrences, which correspond to the actual parameters that are temporary and going to be destroyed. Let us see, what actual parameters correspond to rvalue references. These are functions and expressions, which return standard values of a particular type, not a reference to it. This resolves the issue of the difference between const T& and

T&:

const T& refers to variables and constants;

T&& refers to expressions (including function calls), but not those returning T&.

There is a possibility to state that the contents of a particular variable (say, x) is not needed and can be treated as an rvalue reference, in this case it can be wrapped by using std::move(x). We will discuss it below.

Since two options are available for parameters , the constructors and assignments have two options as well: in addition to the traditional copy constructor and a copy assignment operators, there are now the move constructor T(T&& t) and the move assignment operator T operator=(T&& t).

If, for a particular class, the move constructor and the move assignment operator are defined, the program can take advantage of the function values and instead of copying them, swap them with their contents, as I have shown in the picture.

One issue is particular important when writing a move constructor and a move assignment operator: the contents of the parameter, which is being moved, should remain valid for its further destruction. There are two approaches, which can be used here:

(1) swapping its contents with the target object (as in case of assignment, as I showed in pictures);

(2) setting it contents to a valid empty value, by assigning nullptr to the relevant pointers inside the shell class (often used for a move constructor).

In case of a move constructor, swap can also be considered, if the target is set to an empty value first, before the swap.

A Sample Class Definition with Move Functionality

Let us look at a simple example: a class, which defines an array of double values. The size is fixed, but it can be changed during assignment. You have probably seen many examples of it. But it's good to use it for an illustration: it's functionality is well known. The Array class definition without move semantics may look like this:

Let's add some move functionality. First of all, we shall define the move constructor. You may guess how we shall start. The parameter will be the rvalue reference to the class:

Array(Array&& x) ...

As I mentioned before, the contents of the parameter will be moved to the contents of the object:

Array(Array&& x):m_size(x.m_size),m_array(x.m_array) ...

But it is important that the parameter gets the correct value in return: its contents cannot stay the same, it will be destroyed soon. So, in the body of the constructor, we assign empty values to the contents of the parameter. Here is the full definition of the move constructor:

Now, we shall write the move assignment operator. This is easy. The parameter is the rvalue reference and the body should just swap the contents of the current object with that of the parameter, as we discussed before:

auto operator=(Array&& x) -> Array&
{
Swap(x);
return *this;
}

That's all to it.

Now, there is an issue with the addition operator. It would be good to take advantage of the rvalue parameters as well. Three are two parameters, two options for either of them: there should be four definitions of the addition operator. We have written one, we have to write another three. Let's start with the following one:

friendauto operator+(Array&& x, const Array& y) -> Array

It is pretty straightforward. We just modify the contents of the parameter. We do not need it any more. But we will have to move it at the last moment. So, we write:

The main thing is to return the right contents in the end. If we right return x; the contents of the parameter will be returned as it is. Although the type of the parameter is rvalue, the variable x is lvalue: we haven't done anything to move the value out of the x. If we just return the value of x, there will be no move operation. To do it correctly we have to use return std:move(x);which will ensure that the contents of the parameter will be swapped with the target object. Here is the full definition of this operator:

But, as you have noticed we have written quite a lot of code: four definitions of the addition operator. Is it possible to write less? In order to do this we must look at perfect forwarding.

Reducing the Number of Member Functions: Perfect Forwarding

Perfect forwarding was specially designed to reduc the amount of code the programmer has to write, although it has some other usage as well.

Let's consider the last two definitions of the addition operator. They have very much in common. the only difference is that the first parameter is defined differently: Array&& x and const Array&& x. These two definitions can be replaced by one using the following approach

(1) defining the template template<class T>;

(2) replacing the corresponding parameter definition with T&& x;

(3) in places where std::move(x)is used, put std::forward(x).

In this particular code std::move(x) is not used, so we do not have to use std::forward(x). Here is the result:

In order to combine the other two operator definitions into one we have to change the code slightly, so that the bodies of the two definitions look similar.

But first of all, let's consider some additional information about local parameters. If you look at the first addition operator, you will see that it has a local variable, called z. According to the rules of C++11, when this parameter is returned it will be moved to the new contents (not copied!). You do not need to use std::move(z), to take advantage of move, in fact it will be moved as long as the relevant move constructor and move assignment operator are defined.

In order to make the two addition operators similar, we can create a local variable in both, where we can transfer (copy or move) the contents of the first parameter, and then add the value of the second parameter to this local variable. Here is the modified versions of the two operator definitions:

You may consider that some of our modifications have slowed the code. It is possible that for the sake of efficiency, it would be better to leave the first two operator definitions without modification.

Putting This All Together: Let's Run Some Tests

In order to see what the advantage is of the move functionality, I have to put extra printing into the code in order to see what member functions are called. It will help to measure the efficiency.

After I had done some tests with the previous version of the code I realised that it is necessary to count the total element access instances, instead of only copy operations (one copy counts as two instances of element access: 1 source+1 target). Here is the table of the results (for both GCC 4.7.0 and Visual C++ 2011 in release mode):

Counter

Copy

Move

Element Allocations

1000

800

Element Access

2500

2350

Copy Elision

In certain cases, the C++11 Standard permits omission of copy
or move construction, even if the constructor and/or destructor have side
effects. This feature is called copy elision. Copy elision is allowed in the following circumstances:

in a return statement in a
function when the return value is a local (non-volatile) variable and its
type is the same as the function return type (here, in type comparison,
const and other qualifiers are
ignored); this case I mentioned before;

in a throw-expression,
when the operand is a local, non-volatile variable whose scope does not
extend beyond the end of the innermost enclosing try-block.

when a temporary object
(not bound to a reference) would be copied or moved to a class object with
the same type (type qualifiers are ignored, as in the previous case);

in the
exception-declaration (in the try-catch block), where the parameter in the
catch has the same type as that object in the throw statement (type qualifiers
are ignored, as in the previous case).

In all these cases, the object may be directly constructed
or moved to the target. In all these cases, not only copy, but even move can be
elided. In all such cases, the rvalue
reference is considered first (move), and only then the lvalue reference (copy)
before the copy elision takes place.

Here is a sample program, which uses the array class that we
discussed before:

First of all, if you
look at the gfunction calls, depending on whether f() or
f1() is used as the parameter, the right overloaded function is selected. But this does not prevent the compiler from using the move operation (or to elide it
altogether) in all these calls.

You may look at the instrumented version of the
class, where extra printing is done, in order to trace which constructor is
performed. In the debug mode (Visual C++ 11), without move
functionality, the figures are:

total allocations (elements):16

total elem access (elements):32

In the release mode, the figures are always 8 and 16,
and the compiler elides move or copy,
neither of them is performed.

GCC 4.7.0 always elides move and copy, even in the debug mode.

What does it mean from the programming point of view. First
of all, if you do not write the overloaded function versions with rvalue parameters,
you may still take advantage of the move operation: elision is like optimized move. The general approach would be:

to define both copy and
move constructors;

to define functions or
operators that can take advantage of move (like the operator+ for the
Array class).

In all other cases, when the optimization is not visible, write
your functions the usual, the old way, by using lvalue references (T&&
or const T&&) for parameters.

Value Categories

Every
expression in C++11 belongs exactly to one of the categories: lvalue,
xvalue
and prvalue.
They all can be const and non-const, although some of the values do not have
constness (like literals and enum enumerators). Let us look at all these
categories.

an lvalue designates a function or a
non-temporary object, which can be referenced and is usually either named
or pointed at (for example, variables, function formal parameters, user-defined
constants (using a const declaration) and objects pointed at by pointers;
a value of type T& (where T itself does not contain a reference) returned
by a function is also an lvalue (for example, the function Array& f1() will return an
l-value);

a value of a function or an operator, which is not a
reference (for example, a class object returned by a function; the function
Array f2() returns a prvalue);

an xvalue designates a value of some type
T&& (where T itself does not contain a reference), which is the
result of a user-defined function, std::move, or cast operator, converting to a
T&& (for example, static_cast<Array&&>(x) and the
function Array&& f3()returns
an xvalue).

A glvalueis an lvalue or an xvalue. An rvalue is an xvalue or an prvalue.

Any function formal
parameter, a variable or a user-defined constant (not a enumerator) is an lvalue.

As for prvalue
constants, they can only be class objects, which are returned by functions or
operators. The following function return
a const prvalue:

const Array fc()
{
Array p(2);
p[0] = 3.2;
p[1] = 2.2;
return p;
}

But the problem with
this function is that you cannot assign its result to a variable or a parameter
defined as Array&&. It’s better
not to use const here.

Prvalues, which
are literals and enum enumerators are all considered non-const and can be
assigned to variables and parameters of rvalue reference type. Although in practice we rarely use rvalue references to simple types.

As for xvalues, std::move(x) or
static_cast<Array&&>(x) are good examples. It’s better not define
functions that return xvalues at all, just ordinary class (value T) is sufficient.

In square brackets, the preferable choice of the overloaded function is shown, in case the three
overloads f(T&),f(T&&) and f(const T&) are defined. Unfortunately,
if f(T) and f(T&) are defined together then calling such functions with an lvalue actual parameter can
lead to an ambiguity, which is not allowed.

Just to remind you that all these variables or parameters or constants belongs to the lvalue category, and as such cannot be passed as rvalues; std::move (or conversion) should be used if you want to do so.

When type conversion
takes place (because the type of the value is different the type of the variable), then usually a new, temporary object is created, which is treated
as a prvalue. We are mainly interested in values when T is a class object. The
table shows that when prvalues and xvalues are passed to parameters or assigned
to variables, the operation is very efficient. When lvalues are passed to
parameters which are declared of type T
or const T then copy takes place.

The Microsoft compiler is more flexible in passing
prvalues to T& parameters: it is allowed by the compiler, which is an extension to the C++
standard. It is not permitted by the standard.

In terms of preferences, if T is a class, avoid using
T or const T for parameters, unless you really need a copy of the actual parameters. Use
T, T&& or const T&. In order to achieve efficiency in
passing rvalues, it’s a good thing to define move constructors and move
assignment operators in your classes. Usually, as I mentioned before, T&& can be used to make some
functions more efficient (like an extra code for operator+); otherwise it is
efficient to use const T& to pass
parameters by values.

In terms of the
functions you write (but not move constructors or move assignment operators), it
often does not matter, whether you write T&&
or const T& forprvalues, because copy elisionwill make parameter passing very
efficient anyway. But const T& allows
you to pass lvalues by reference, which is very efficient.

In terms of function
return values: use T, T& or const T&.

If you look at the first line of the table again, this is where the choice between elision, move and copy is important. The general rule for the assignment is that if the source is a temporary object (prvalue) and the target is an variable, a move assignment will take place. And for the initialization or parameter passing, if the source is a temporary object then a copy elision takes place. But there can be a case where the source is a "hidden" lvalue. Consider the following function:

Array g(Array& y)
{
return std::move(y);
}

Here is a code fragment that uses this function:

Array x(1);
v[0] = 2.5;
Array z(g(x));

In this case, x is still a valid lvalue, not a temporary, which is referenced by the y formal parameter. As a result, when z is initialized move construction will take place.

Here are some conditions for an actual parameter when an overloaded function with an rvalue reference is chosen:

a call to a function or an operator, which does
not return an lvalue reference (T&);

invoking a constructor, which creates a temporary
object;

a
conversion has to be applied to the const lvalue reference parameter, which creates a new, temporary object;std::move(x)
or static_cast<T&&>(x) is used;

a literal is used (for example: 2.0, true,
‘A’), in which case a temporary object is created to contain it.

Here the Container class is
used for a temporary object. We have created two access functions for the Array
object: getArray and moveArray. The first one is more efficient: it uses
the move operation. But we explicitly gave them different names.

It would be nice to be able to define overloaded access functions, which perform differently depending on whether
the object is temporary or not. In C++11 Standard it is possible and can be
done using ref-qualifiers (& or &&):

The rule is that if you use a ref-qualifier for one of the overloaded functions you have to use ref-qualifiers for all the others.
Unfortunately, this feature is not available in Visual C++ 11 or in GCC 4.7.0.

Other Uses of Perfect Forwarding

In addition to the cases discussed before, there are
following cases, where it is convenient to use perfect forwarding:

to define a function template which will create
an object of a class and possibly provide extra manipulations with it;

to create a wrapper for a function call.

The first case is often used in by emplace(...) methods in the new C++11 STL, which both create and
store objects in containers.

For
example, for v of class vector<A> , instead of using v.push_back(A(p1, p2, …,pn)), we can use v.emplace_back(p1,p2, …, pn).In order to provide this feature for an arbitrary
number of parameters in the constructor, the variadic
templates are needed. They are not available in Visual C++ 11, but they exist in GNU C++
4.7.0.

Another example, here is the definition of make_unique, which is a function template that returns a unique pointer to the given class:

There is a similar case of a wrapper, where the function is provided as a parameter. This case is not the exact perfect forwarding: the parameter type is derived not from the value x, but from the function. In this case you may use either static_cast<T>(x)or std:forward<T>(x) :

If we use, as this program's parameter, the file myfile.txtwith the following contents:

My File
Some types are not amenable to copy semantics but can still be made movable. For example:
(1) fstream
(2) unique_ptr (non-shared, non-copyable ownership)
(3) A type representing a thread of execution

the program will print the lines of the file (except for the first one). But if the first line is not "My File", the program will report an error.

The main feature of this program is the function OpenMyFile, which returns the value of the local variable file. As you may remember, the return value is such cases can be moved. This value will be moved to the variable myFilein the main program.

Unfortunately, the last program works only in Visual C++ 11. In GCC 4.7.0, the move semantics is not implemented for fstreams.

Pablo. Thank you for the message.They were there last night when I published the article. For some reason the uploaded files have disappeared (but the names are still there). I have to upload them again.