Portfollio

Menu

Understanding Move Semantics and Perfect Forwarding: Part 1

An Introduction to Rvalues and Lvalues

With the introduction of C++ 11 comes the ability to further improve the performance and flexibility of C++ applications thanks to the introduction of move semantics and perfect forwarding. Two concepts that rely on rvalue references.

Before discussing these new concepts an understanding of rvalues and lvalues is required. These concepts are themselves not too difficult, but the evolving nature of C++ and the changes in definition of these values as the language has matured can make them rather confusing for new developers.

At the beginning rvalues and lvalues were defined in such a way that an rvalue very simply meant something that appeared on the right-hand side of an assignment, and an lvalue on the left. For example:

int num = 3;
4 = num; // 4 is a literal which is an rvalue and num is an lvalue thus the following code isn’t valid

But something like the following is acceptable:

int num = 4; // num is an lvalue and 4 is an rvalue

As time moved on and the C++ language evolved, so too did the definitions of rvalues and lvalues. Unfortunately, their names did not evolve with it. In more modern versions of C++ rvalues and lvalues took on new meanings.

Lvalues and rvalues became something used to describe the results of an expression (An expression being any code that returned a value) and as such an expression would be described as either an lvalue expression or an rvalue expression.

To the compiler this defines amongst other things how the result of an expression is stored. To a developer, which I am assuming you are, it basically means that the lvalue has a name and an identifiable location in memory that can be accessed using ‘&’. An rvalue is something that does not have an identifiable location in memory and thus it cannot be accessed with ‘&’.

An easy way to identify whether an expression returns an rvalue or an lvalue is to determine if you can take address of the value. If you can its an lvalue otherwise it’s an rvalue.

Rvalues are temporary and have short lifetime where they exist within the expression they were created. Because of this C++ doesn’t allow access to them through the address of operator due to the amount of problems it could occur by trying to access the address of short lived data.

That about wraps it up for what rvalues and lvalues are, but I can assume that the above definition does little to explain their actual use in writing of C++ applications. I will attempt to amend this in the next section by explaining how they can cause issues when writing code.

As previously mentioned you cannot obtain the address of an rvalue because C++ won’t allow it. This means that when writing code there are a lot of values that we cannot have references to. Literals, functions that return by value, and temporary objects are just a few examples. This means that a reference cannot be stored to these values, but more importantly these values cannot be passed to functions that accept reference types.

Unfortunately, passing by reference is a common method of improving the performance of an application as it avoids the need to copy data between memory locations, which if there is a lot of data can take up too much time in our time critical applications.

Therefore, to avoid these issues we can take several steps. Simply try and avoid passing rvalues to functions which means the following code:

The convenience of using rvalues is lost to maintain performance but at the cost of readability and time. A second option is to pass by value but that could potentially mean hindering performance. A third option exists, and that is to pass by reference but making the parameter of a function const.

int sum(const int& a, const int& b) { return a + b;
Sum(3, 5);

C++ allows an rvalue to be assigned to a reference which is an lvalue if and only if that lvalue is defined as const which stops it from being manipulated. This means the rvalue is technically an lvalue within the scope of the function, and the function is now able to accept rvalues whilst still preserving pass by reference.

An obvious problem that arises is that if a function needs to manipulate a reference then it can’t without overloading that function with a non const variant when wanting to pass an lvalue. Rvalues bring up additional problems in C++ which weren’t addressed until C++ 11.

For instance, C++ has a tendency to overindulge when it comes to copying values and there are issues with forwarding values to functions called within template functions. These issues are combated with move semantics, and perfect forwarding which will be a lot easier to understand now that you have an understanding of rvalues and lvalues and will be the focus of the next couple of articles.