C++11 rvalue references usage

Rvalue references are one of the biggest feature introduced to the c++11, but I found it rather difficult to understand and to find a usage for them. It was the issue which was very similar to the understanding c++ traits I had. Now, I’d like to write down all what I’ve learned, in order to remember it better and to share the way I understand it the best. This article is a very quick introduce to why we need rvalue references and why they are awesome!

Rvalue vs lvalue

The formal definition of how specific expressions are called is pretty complicated, but to make it straightforward, just remember, that lvalue is a value that resides in a memory. Rvalue is not an lvalue, it’s temporary.
int X = 1; // X is lvalue, 1 is rvalue
int Y = X+2; // Y is lvalue, X+2 is rvalue
int Z = int(20); // Z is lvalue, int(20) is rvalue
20 = X; // 20 is rvalue, X is lvalue. We have got ERROR here, we cannot assign lvalue to rvalue!

Lvalue reference vs rvalue reference

To get a reference to an lvalue we use & operand. To get a reference to an rvalue we use && operand.

When do we use rvalue references?

We use them to detect if the function argument has been passed by an lvalue or rvalue. Consider we have a class Room that stores Items. For future use, let the Item contains some data.
class Item
{
private:
    int* data;
    int dataLen;

public:
    explicit Item(int inDataLen)
    {
        std::cout << "Item constructor \n";
        dataLen = inDataLen;
        data = new int[dataLen];
    }
    
    ~Item()
    {
        std::cout << "Item destructor \n";
        delete[] data;
    }
};
 
class Room
{
private:
    std::vector<Item> items;

public:
    Room()
    {
        items.reserve(100);
    }
    void AddItem(const Item& item)
    {
        std::cout << "Adding item by lvalue \n";
        items.push_back(item);
    }
    void AddItem(Item&& item)
    {
        std::cout << "Adding item by rvalue \n";
        items.push_back(item);
    }
};
Now you can add Items to Room:
Room room;
Item item(100);
 
room.AddItem(item); // Adding item by lvalue
room.AddItem(Item(200)); // Adding item by rvalue
Without a function with rvalue reference as an argument the code would work as well. But we wouldn’t know if the item has been added by lvalue or rvalue.

What does it give us?

It gives us information if, when adding an item, we must copy all of it’s data or simply move. The problem with previous versions of c++, without rvalue references, is that when we run such code as:
std::vector<Item> items;
items.push_back(Item(100));
It runs an Item class constructor, then copy data to the vector and destroys the temporary object. The copy step is just a waste of resources, when we can simply pass a pointer to the data. This is why, next to the copy constructor, we introduce a move constructor!
Item(const Item& otherItem)
{
    std::cout << "Item copy constructor \n";
 
    dataLen = otherItem.dataLen;
    data = new int[dataLen];
    memcpy(data, otherItem.data, dataLen);
}
 
Item(Item&& otherItem)
{
    std::cout << "Item move constructor \n";
 
    dataLen = otherItem.dataLen;
    data = otherItem.data; // Just passing a pointer, no copying!
 
    // We must zero the otherItem, because all the data
    // has been moved to the current one.
    // The data in the old object shouldn't be accessible.
    otherItem.dataLen = 0;
    otherItem.data = nullptr;
}

std::move

You can force moving objects by using the std::move function, which casts the object to an rvalue reference:
std::vector<Item> items;
Item item(12);
items.push_back(std::move(item));
Only remember, that after this operation the item is no longer valid here! We’ve just moved it somewhere else.

Also – remember that std::move doesn’t guarantee that the item will be moved. For example – if the item is const, the move will fallback to copy.

Perfect forwarding

We are almost done, but there is something wrong with the code we’ve just wrote. You might notice, that even if we run AddItem function with the rvalue reference as the argument the item is added to the vector by copying, not moving. Why is that?

This is because the Item&& item is an lvalue! It resides in a stack memory! That’s why when passed to push_back it goes to a copy constructor. To pass the arguments through to another function whilst retaining the rvalue nature simply use the move cast. The fixed AddItem function should looks like this:
void AddItem(Item&& item)
{
    std::cout << "Adding item by rvalue reference \n";
    items.push_back(std::move(item));
}
But what if we want to write AddItem function as a template? We can do this like this:
template <typename T>
void AddItem(T&& item)
{
    items.push_back(std::forward<T>(item));
}
Ok, now this might be getting a little confusing, so let’s explain all of this.

First of all – this one template function can be deduced to accept both lvalues and rvalues. This creates a problem – as we’d like to move forward objects that came to us as an rvalue, but copy objects that come to us as an lvalue.

This is where we introduce the std::forward. In contrast to the std::move which is unconditional cast that will always cast to the rvalue, the std::forward is a conditional cast that will cast only when the item argument came to us as an rvalue.

In other words - std::forward allows to maintain the rvalue/lvalue nature of the passed argument in templated functions. Basically – if you have templated function you will probably use the std::forward to pass the value further. This is called the perfect forwarding.

Summary

And that’s all. The whole idea of rvalue references is to provide much more optimal way to pass complex structures through containers.