Modern C++ Features – Move Constructor and Move Assignment

Contents

In my last post I gave a little introduction to move semantics and a very brief description of rvalue references. This time I will cover two special functions that are based on those language features and are key to integrating them into each and every C++ program: The move constructor and move assignment operator.

What is moving, exactly?

Basically, when we copy an object, we end up having two objects with the same value. For plain old data e.g. and int, that means we have two chunks of memory, each typically 4 or 8 byte in size, containing identical patterns of bits – the same value.

CopyWhen we have more complex objects, like a std::vector<int>; the bit pattern is not identical any more, but they semantically are the same – both are either empty or they contain a pointer to some memory, and in that memory reside the same number ob elements with pairwise equal values.

When we move a value from an object a to another object b, things get a little different: we want the (semantic) value of a prior to the move operation to be equal to the semantic value of b after the operation. We don’t care about the value of a after the move. It only must not interfere with b afterwards, e.g. when it is destroyed.

Since we don’t care about the value afterwards, copying a to b is a valid move operation, albeit not necessarily the most effective. For an int, copying the bits is the fastest way to get the value from one chunk of memory to the other, so moving an int usually means copying it.

MoveFor complex objects, copying usually is not the most effective way to go. In the case of std::vector<int> described above, the most effective way would be to give b the memory previously owned by a, including the contained values. Since a may not interfere with b after the move, it may not hold on to that memory, so the easiest way is to set it to be empty.

The syntax

In C++03 we already had copy constructor and copy assignment operator. They are declared with a reference or, more usually, a reference to const of the type as parameter. In C++11 we get the two move operations by providing a rvalue reference as parameter. Since we usually want to steal the guts of the moved objects, the rvalue reference should be non-const:

Class Moveable {
public:
  Moveable(Moveable&& other); //move constructor
  Moveable& operator=(Moveable&& other); //move assignment operator
};

Help from the compiler

As with the copy operations, the compiler will help us generating the move operations as well. The generated move constructor and move assignment will work analogous to the generated copy operations: They will perform an element-wise move.

With the two move operations the known “Big three” compiler generated special member functions become the “Big five”: Copy constructor, move constructor, copy assignment operator, move assignment operator, destructor.

Move operations are only generated if none of the “Big five” has been declared by the user. In addition, if you declare a move constructor but no copy constructor, the copy constructor is implicitly defined as deleted and objects of the class are moveable, but not copyable.

There’s a bunch of other rules in the standard that affect the generation of move and copy operations. Therefore, in order to not fall into any traps and make the intent clear to the reader, it’s best to stick to the Rule of All or Nothing:

Declare either none or all of the “Big five”. If you have to declare them, consider to define them as `default` where appropiate.

If no move operation for a class is present, either because it can not be generated or because a user declared copy operation does prevent the generation, then the compiler will always copy.

As described above for integers, a move of a built in data type is simply a copy. So if you have a class that consists of only such elements, the generated copy and move operations will do the same.

Writing your own move operations

We often write classes that can benefit from move operations, e.g. because they have to manage some resources. However, many of those classes can benefit from already existing classes that do the resource management for us, like smart pointers, std::vector etc.

If you have need for a class that can move one of its resources or responsibilities and there is no class to handle it for you, then it’s best to write a small class that does only the resource handling including the moves, and include an instance of it in your bigger class.

That way you create small, reusable classes that have a single responsibility (handling the resource, including moving it), and don’t need to load that burden on the bigger class, because the compiler generated moves of that class will just do the Right Thing.

Prefer to compose your classes from smaller objects that encapsulate special move semantics.

That’s it for today. Next week I’ll write the promised wrap up about how to use moveable classes in data sinks.

 

Previous Post
Next Post

Leave a Reply

Your email address will not be published. Required fields are marked *