My first exposure to move semantics was seeing the weird double ampersand “T&& t”. After enquiring to a colleague what it is, I was very vaguely told it was to do with “objects stealing resources from one another”. other colleagues told me that a function called std::move() was useful for taking resources from temporaries, whilst another told me it was used for changing the ownership of objects such as c++ 11 unique pointers. I was also told that using std::move() will *always* destroy the original object by leaving it with no data, and that actually it doesn’t move anything.. as confusing as all of that sounds.
After research and experimentation, along with discussions with others, I now know those initial descriptions were, not wrong, but incomplete. Here I provide some code examples which I hope will demonstrate what this interesting, yet seemingly often misunderstood topic is about.
L value and R values.. what do you mean?
Basically these values are whats to the left and to the right of the ‘=’ in the above expressions. i and j are lvalues, as they have a ‘handle’ or variable name, whereas the ‘1’ and the function call GetMyInt() are rvalues. Note that if ‘GetMyInt()’ returns a reference, then it can function as an l value, because you can assign a value to the reference you are returning.
If we had a constructor call to a class, such as a class called ‘Entity’, then this is another example of an R value:
We refer to the ‘1’ and ‘GetMyInt()’, and Entity() as pure r values, or pr values. There is another type of R value, called an ‘x value’, standing for eXpiring, which we’ll come to later when we explain std::move().
So by ‘R value’ reference, yes
What’s with the && then? Is it a reference to a reference?
No, it’s an R value reference. Since C++ 11 we can take a reference to the r value, this is where the ‘&&’ double ampersand comes into play. Consider these functions:
The top overloaded function is called by using an lvalue, such as in the following:
So, if you’re thinking that passing in the R value reference will allow us to use the bottom overload with the ‘&&’, then you thought correct:
However, you may also be thinking “well, that seems a bit pointless”. Well it is. You have passed in an R value hence cannot access it nor see the new values in the entity object once the function returns. In this case it would be helpful if we could somehow cast our l value into an r value… Well we can. This is exactly what std::move() is for.
std::move, casts a variable into an expiring r value reference
So yes, std::move doesn’t really move anything. Here is its code:
So it is using a static_cast<t>() to cast the input variable of type Ty into a variable of type Ty&&. This reference to the new r value was created through a cast, and not via a literal like our pure r values, it is not a pr value. It is often referred to as an x value, or eXpiring value, as it is expected to have its “resources” moved, and is being treated as reaching the end of its life.
So if you wish to move something, and you have a an l value, then std::move is exactly the tool you can use to turn the l value into an r value reference and start calling functions of type T&&. So taking our example function
To call this with an lvalue, if we really wanted to, we can use std::move.
Please note though that this is a toy example to demonstrate std::move, which was not designed for this purpose. We are not moving anything – all the resources or internals within entity are still there. Hence, in this case nothing will be lost, debunking any myths that std::move will, necessarily, clear out an object after use. To properly move them, as intended, we must define a move constructor and a move assignment operator in our class.
Defining move constructors and move assignment operators allowing us to ‘move’ our objects
By ‘resources’ we are referring to dynamically allocated objects, such as pointers instantiated with new or malloc, or automatically allocated objects that might contain their own dynamic objects. In the latter case, if these are custom objects they should have their own move constructors and assignment operators, and if they are collections within the standard template library, then they will have these special member functions anyway. Hence, regarding whether we need to define our own move functions, the same rules apply to when we must define our own copy constructors and copy assignment operators. For objects containing only automatically allocated data, then the default copy constructor and copy assignment operator will suffice and we don’t need to define our own. When our objects have dynamically allocated data that we do need to define a copy constructor and copy assignment operator (or we end up with just copied pointers and shallow copies of our dynamic data). This rule also applies to move constructors and move assignment operators.
Lets have a look at our class Entity
So here we have a class with two automatically allocated floats, a pointer to a dynamically allocated object, a dynamically allocated array to some ints and to be a bit more modern, I have put a smart pointer (std::unique), that will clear up after itself, to the same object. The array buffer and dynamic object must be deleted in our destructor to prevent leaks, and of course the unique pointer will clear up for us after it goes out of scope. The copy assignment and copy constructors are defined as follows:
It is necessary to re-allocate our objects with copies of the originals, and in the case of the array use the c style memcpy function. This is expensive, and we may not want the original object and just the new ‘copied’ one. In this case moving the data is the preferred option:
As you can see, now all we have to do is simply assign the member pointers of our other (moved from) object to our new moved to object, like we would with a shallow copy. As Entity&& other is expiring due to it being a pr value or an x value (after std::move), then this doesn’t matter anymore. A problem arises though when other does expire, as all its resources will be freed. However, you cannot delete what has been set to a nullptr, and so this is why we set other.m_Buffer and other.m_memberObject to null. A safety check therefore, to ensure we don’t null our own member variables if accidentally trying to move the same object, is to assert that this is not equal to other. This will probably be sensible in the copy constructor too, although no data would be lost.
As you can see, regarding our smart pointer m_memberUniquePointer we, are calling std::move on this too. Why? Because – std::unique pointer has defined its own move constructors and move assignment operators. As a unique pointer can only have one owner, then std::move can be used here to transfer that ownership. Many such publically accessible objects in the standard library, including vectors, maps and strings, also have these special member functions defined, allowing us to transfer their resources without copying.
Lets take this code, which involves passing a pure r value to a templated function. What constructor do you expect to be called, the move or the copy?
So as you can see from the output, the copy constructor is actually being called when our entity is passed into this function. The culprit is the template system in our function but also in emplace_back: C++ reference collapsing rules are in play, and our Entity&& is collapsed into Entity&.
What we need is to be able to supply a function that can take a universal reference, be it && or &. Well this is when we can use std::forward, which can maintain the type (hence why this is sometimes referred to as a “forwarding reference”):
This will now call our move constructor as intended.