Introduction to C++ 11 Series – Part 9, R-Value, L-Value, Move Semantics and Perfect Forwarding

April 3, 2013

Take a deep breath; this is a long post about one of the most important and most complex features of C++ 11.

One of the most performance-boost features of C++ 11 is the new move semantics addition to the language. This feature is based on a new reference – an R-Value reference. In this post, I am going to explain what R-Value (and L-Value) is, why we need it, and how we can use it to enable move constructor & move assignment operator. We will also see the std::move function and talk about perfect forwarding and the std::forward function.

Motivation

C++ as opposed to C# & Java provides the ability to create a copy constructor and assignment operator for an object. This enables a user-defined deep copy of an instance, somewhat similar to a C#/Java clone method. The main difference from a clone method is that the compiler calls the Copy-constructor function implicitly when an instance of the object is passed by value as a parameter to a method or as a return value from the method. This is a very important behavior for application correctness for several reasons:

  1. When returning a pointer or a reference of a function local variable, its memory location on the stack is freed, hence it is important to return a copy of this variable (or don’t use the stack but a heap based object)
  2. When passing an object by pointer or reference, anyone can change the object’s values. If this object is a part (field) of another object, it may change without the supervision of its owner. I name a function that returns an inner state of an object a revealer. This is a major problem in C# and Java, and the correct implementation is to use the immutable pattern. In C++ sometimes, a const qualifier can help, but a clone provides better decoupling. Future changes of the original object will not affect an older copy and vice versa.
  3. Copy-constructor is a necessary mechanism for stack based instance lifetime management.

These are good reasons for using copy constructor and assignment operator, however copying objects has a major performance impact. Especially for large objects or those that allocate heap memory in their constructor and free it in the destructor, such as the case of STL containers.

Example:

 

#include "stdafx.h"

#include <iostream>

#include <vector>

#include <crtdbg.h>

#include <random>

#include <algorithm>

#include <iterator>

#include <chrono>

 

using namespace std;

using namespace chrono;

 

class CCtorCounter

{

public:

       CCtorCounter()

       {

              generate_n(back_inserter(payload), 10000, rand);

       }

      

       CCtorCounter(const CCtorCounter &o)

       {

              ++counter;

              cout << "CCtor:" << counter << endl;

              copy(begin(o.payload), end(o.payload), back_inserter(payload));

       }

 

private:

       static int counter;

       static mt19937 rand;

       vector<int> payload;

};

 

int _tmain(int argc, _TCHAR* argv[])

{

       _CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);

       _CrtSetReportFile(_CRT_WARN, _CRTDBG_FILE_STDOUT);

 

 

       _CrtMemState mem_start, mem_end, mem_diff;

 

       _CrtMemCheckpoint(&mem_start);

 

       CCtorCounter in;

 

       auto start_time(steady_clock::now());

 

       auto o(Foo(in));

 

       auto end_time(steady_clock::now());

 

       cout << "Execution time: " <<
              duration<double, milli>(end_time start_time).count()

                                                     
<< " ms" << endl;

 

       _CrtMemCheckpoint(&mem_end);

 

       if (_CrtMemDifference(&mem_diff, &mem_start, &mem_end))

              _CrtMemDumpStatistics(&mem_diff);

      

       return 0;

}

 

The result (Debug Build):

clip_image001[4]

 

We can eliminate one copy by passing the input parameter as const reference:

 

CCtorCounter Foo(const CCtorCounter &in)

{

       CCtorCounter x;

      

       return x;

}

 

The result (Debug Build):

clip_image001[6]

 

To avoid copying a temporary object – an object that serves as a return value from a function, one can create the object in the caller context and pass it as an out parameter, or better, one can use a smart pointer (shared or unique) which will result a heap allocation. However, this kind of code is harder to write and maintain and heap allocation is less effective compared to stack, especially in a heavy concurrent application.

C++ 11 provides a way of using stack based passing by value instance semantics, without the overhead of deep copy of temporary objects. In the sample above, we know that x is a temporary object. We also know that after the call returns from Foo, this object will be destroyed. The compiler generates code that copies the guts of x and then destroys x! Wouldn’t it be better if we can take the guts of x and put it in o:

auto o(Foo(in));

To be able to do so, we need a way to distinct passing temporary objects vs. non-temporary objects. This is the main reason to the addition of a new reference type to the language, the R-Value reference (&&). I will explain all the details later, but for now, let’s create another constructor, a move constructor which takes a temporary object and moves its guts to the new object:

CCtorCounter(const CCtorCounter &&o)

{

       cout << "Move Ctor" << endl;

       payload = std::move(o.payload);

}

The result:

clip_image001[8]

 

As you can see, we have eliminated all the calls to the copy constructor.

This is a very simple example and in fact, compiling the same code in release mode would had resulted the same performance, even without the new move-constructor, since the compiler can use the return-value-optimization and VS C++ compiler uses it. However, there are many complex cases which move semantics provide huge performance gain, such is the case of manipulating strings, or using algorithm that copies elements in STL containers.

 

L-Value and R-Value References

L-Values & R-Values got their names from the left side and the right side of the assignment expression. This is the wrong way to think about them. The better way to distinguish between the two is to understand their lifetime and memory persistence. An L-Value is an expression with memory persistence while an R-Value is a temporary expression.

 

clip_image001[14]

 

Before C++ 11, there was no way to refer to an R-Value. Trying to do so yielded an L-Value. Any assignment, or passing an argument by value or even by reference (single & – now called an L-Value reference) causes the expression to be copied (persisted in memory) and become an L-Value, This makes sense since you need the temporary expression to be valid longer than the compiler needs it. In C++ 98/03 it is valid to take the address of a parameter, even if the parameter is a reference (L-Value reference) or a const reference. Actually even with R-Value reference parameter in C++ 11, the compiler treats the parameter as an L-Value in the body of the function, mainly because you can refer to it in many places and R-Value references tend to be destroyed.

As you can see, keeping an R-Value as an R-Value is tricky, in many cases it will become an L-Value.

The following code snippet shows how the compiler treats R-Values and L-Values:

 

void print_reference(const string& str)

{

       cout << "const string& :" << str << endl;

}

 

void print_reference(string&& str)

{

       cout << "string&& :" << str << endl;

}

 

void rvalue_references_overload()

{

       string s("s: alon");

       print_reference(string("alon"));

       print_reference(s);

}

The result is:

clip_image001[16]

 

The compiler knows which method to call. Now that we know how to distinguish between temporary (R-Value) and non-temporary (L-Value), we can write a move constructor and move assignment operator. Let’s create our own simple and naïve string class and implement both:

 

class my_string

{

private:

       char* _string;

       int _length;

public:

       explicit my_string(const char* str)

       {

              if (str == nullptr)

              {

                     _length = 0;

                     _string = nullptr;

                     return;

              }

              _length = strlen(str) + 1;

              _string = new char[_length];

              strcpy_s(_string, _length, str);

       }

 

       my_string(const my_string& other)

       {

              _length = other._length;

              if (other._string == nullptr)

              {

                     _string = nullptr;

                     return;

              }

 

              _string = new char[_length];

              strcpy_s(_string, _length, other._string);

       }

 

       my_string& operator=(const my_string& other)

       {

              if (this == &other)

                     return *this;

              delete[] _string;

             

              _string = nullptr;

              _length = other._length;

 

              if (other._string != nullptr)

              {

                     _string = new char[_length];

                     strcpy_s(_string, _length, other._string);

              }

       }

       ~my_string()

       {

              delete[] _string;

              _string = nullptr;

              _length = 0;

       }

 

       //move functions:

       my_string(my_string&& other)

       {

              *this = std::move(other); //std::move preserves &&

                                      //semantics so operator=(&&) is called

       }

 

       my_string& operator=(my_string&& other)

       {

              if (this == &other)

                     return *this;//handle self-assignment

 

              _string = other._string;

              _length = other._length;

             

              other._string = nullptr;

              other._length = 0;

 

              return *this;

       }

};

 

my_string is a simple string class implementation. There are two new methods, the move constructor and the move assignment operator. The move constructor gets the temporary (R-Value reference) and builds the object using the assignment operator. It uses the std::move to force this call. I will talk about std::move later. The move assignment operator first checks if we are assigning from the same instance, a case that you should handle also with the regular assignment operator. When calling from the move constructor this will never be the case. Now the move assignment operator copies the internals of the R-Value object. The last step is to make sure that the temporary object destructor will not free the stolen values, simply by nullifying them. That is it; our my_string class can be used in a very efficient way when passing temporaries by value.

 

The std::move function

template< class T >

typename std::remove_reference<T>::type&& move( T&& t );

 

std::move is a template function that converts an L-Value into an R-Value. The std::remove_reference<T>::type is a tricky function that removes any reference from T if exists. It does it by using three overloads; all of them use a typedef to create the non-reference type:

template< class T > struct remove_reference      {typedef T type;};

template< class T > struct remove_reference<T&>  {typedef T type;};

template< class T > struct remove_reference<T&&> {typedef T type;};

 

So the line typename std::remove_reference<T>::type&& defines an R-Value return type, no matter if the original type was a value, an L-Value reference or an R-Value reference. Move takes an argument of an R-Value reference; however, it is ok to pass a value, or an L-Value reference as an R-Value reference, because R-Value reference preserves the original type and in any case, in the body of the method an R-Value reference becomes an L-Value reference. The body of the function is just a simple casting to the return type, which makes the object a temporary: 

template<class T> inline

typename remove_reference<T>::type&& move(T&& t)

{     

       return ((typename remove_reference<T>::type &&)t);

}

With this knowledge, when we look at the implementation of the move constructor:

*this = std::move(other);

We understand that we assign an R-Value reference, hence invoking the move assignment operator. You might think: “The original parameter for the move constructor is an R-Value reference, why do we need to cast it again to be an R-Value by the std::move function?” If you recall I have mentioned that the compiler treats R-Values inside the body of the method as L-Values, hence we need to force it to be an R-Value again. Also sometimes, you may want to convert an L-Value explicitly to become an R-Value. This is what the uniqe_ptr<T> class does.

 

Perfect Forwarding

Another interesting feature that came from the introduction of R-Value references is the ability to pass a parameter between functions without losing its type. As I showed you in this post, the compiler converts an R-Value reference to become an L-Value reference in the body of the function. This means that if you use an intermediate function, you may lose the performance gain of the move semantics because the R-Value reference will be converted to an L-Value one. Perfect forwarding forces the compiler to keep the original type of the object, be it a value, an L-Value reference, an R-Value reference or any combination of those with the const qualifier. It is achieved by using a set of reference collapsing rules, to be discussed soon.

To pass a parameter with a perfect forwarding, use the std::forward<T> function:

template< class T >

 T&& forward( typename std::remove_reference<T>::type& t );

template< class T >

 T&& forward( typename std::remove_reference<T>::type&& t );

 

The function has two overloads and they are somewhat similar to std::move. When you use it, you must provide the template parameter type.

For Example:

template<typename T>

class Target

{

 

public:

       void Action(typename remove_reference<T>::type &) { cout << "Action(T&)" << endl; };

       void Action(typename remove_reference<T>::type &&) { cout << "Action(T&&)" << endl; };

};

 

template <typename T>

void old_forward(T& value)

{

       cout << "old_forward(T& value) ==> ";

       Target<T> target;

       target.Action(value);

}

 

template <typename T>

void wrong_new_forward(T&& value)

{

       cout << "new_forward(T&& value) ==> ";

       Target<T> target;

       target.Action(value);

}

 

template <typename T>

void new_forward(T&& value)

{

       cout << "new_forward(T&& value) ==> ";

       Target<T> target;

       target.Action(std::forward<T>(value));

}

 

void rvalue_references_forward()

{

       string s("Alon");

       cout << "old forward:" << endl;

       cout << "————" << endl;

       cout << "string s(\"Alon\"); ==> ";

       old_forward(s);

       cout << "old_forward(string(\"Alon\")) ==> ";

       old_forward(string("Alon"));

       cout << endl;

 

       cout << "wrong new forward – not a perfect forwarding:" << endl;

       cout << "———————————————" << endl;

       cout << "string s(\"Alon\"); ==> ";

       wrong_new_forward(s);

       cout << "new_forward(string(\"Alon\")) ==> ";

       wrong_new_forward(string("Alon"));

       cout << endl;

 

       cout << "new perfect forwarding:" << endl;

       cout << "———————–" << endl;

       cout << "string s(\"Alon\"); ==> ";

       new_forward(s);

       cout << "new_forward(string(\"Alon\")) ==> ";

       new_forward(string("Alon"));

}

 

The Target class has two overloaded methods. The first takes an L-Value reference and the second takes an R-Value reference. To be sure that they are defined to take the correct reference type I am using the type_trait remove_reference<T>::type. To show the perfect forwarding, I have created three intermediate functions. The first old_forward takes the parameter by L-Value and passes it to one of the Target’s overloaded functions. The second takes the parameter as an R-Value reference, but it passes it without the use of the std::forward method, hence revert to the old L-Value behavior. The last one is the correct implementation; it takes the value as an R-Value reference and uses the std::forward to preserve the original reference type.

The result:

clip_image001[18]

 

The implementation of std:forward is:

template<class T> inline

T&& forward(typename remove_reference<T>::type& t)

{      // forward an lvalue

       return (static_cast<T&&>(t));

}

 

template<class T> inline

T&& forward(typename remove_reference<T>::type&& t)

{      // forward anything

       static_assert(!is_lvalue_reference<T>::value, "bad forward call");

       return (static_cast<T&&>(t));

}

 

As you can see, it is quite similar to std::move, but you are the one providing the template type parameter.

What these functions do is casting to an R-Value reference. Since R-Value reference preserves the reference type, the forward passes the same type of T, the template parameter, which in a template intermediate function is usually the input parameter type.

To understand why template functions that take R-Value reference can be suitable for both L-Value references and R-Value references bind, we need to understand the reference collapsing rules. Prior to C++ 11, it was forbidden to use a syntax such as this: void Foo(int & & x). Now it is legal. When the compiler sees many ampersands, it uses these rules to decide whether the result is a single ampersand (L-Value reference) or double ampersand (R-Value reference):

T & &  è T&

T& &&  è T&

T&& &  è T&

T&& && è T&&

For template functions that take an R-Value reference, there are special parameter deduction rules:

template <typename T>

void new_forward(T&& value)

 

  1. When T is called with an L-Value reference, T holds a single ampersand and according to the rules above, three ampersands become one, hence the function takes an L-Value reference: (T&& &value) è (T &value)
  2. When T is called with an R-Value reference, T holds two ampersands and according to the rules above, four ampersands become two, hence the function takes an R-Value reference: (T&& &&value) è (T&& value)

With this knowledge in mind you should use perfect forwarding whenever you create a mediator function to preserve the original parameter type.

 

R-Value References and STL

In Visual Studio 2010 and in Visual Studio 2012 Microsoft has rewritten STL to support R-Value references, the move semantics and perfect forwarding. Containers such as vector use move semantics to improve performance. STL code will call your move constructor or assignment operator if you define one. Some algorithm such as std::swap takes advantages of the std::move function:

template<class T> inline

void swap(T& left, T& right)

{      // exchange values stored at left and right

       T tmp = move(left);

       left = move(right);

       right = move(tmp);

}

 

The new unique_ptr<T> is a non-copy-able move semantics type that guarantees that there will be only one smart pointer that points to the instance, because it moves the real pointer in any assignment, function call or function return. This makes the old auto_ptr<T> obsolete.

 

Summary

Yes, you have reached the end of this post and you have survived Smile. You can read more about R-Value, Move, and Perfect Forwarding here , here, and here.

 

 

Add comment
facebook linkedin twitter email

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

3 comments

  1. maillot 2013April 23, 2013 ב 03:11

    I really enjoy the blog.Much thanks again. Great.

    Reply
  2. sunaezp@gmail.comMay 2, 2013 ב 14:49

    Hi, I think your website might be having browser compatibility issues. When I look at your website in Chrome, it looks fine but when opening in Internet Explorer, it has some overlapping. I just wanted to give you a quick heads up! Other then that, very good blog!

    Reply
  3. seo serviceMay 27, 2013 ב 19:25

    J4nMbl I loved your blog post. Fantastic.

    Reply