Share via


Rvalue Reference Support in Visual C++ 2010

I’m Wesley Yao, from the MSDN support team. Last week I took a look at the new features in VC++ 2010 and did some testing. Today I’m going to share information on the Rvalue Reference to you based on my understanding.

Rvalue Reference Declarator (&&) is an amazing feature in VC++ 2010. It gives us the ability to overload a function which can distinguish if the passing parameters are Rvalues.

Lvalue and Rvalue

First, let me introduce the Rvalue and Lvalue in C++.

Lvalues are single expressions which can be referenced in other place of the program, like obj, *ptr and ptr[index]. Rvalues are temporaries which cannot be referenced in other places of the program since it has no a specific name, like 2010, x + y and std::string(“Hello”).

For example, we have a definition like the one below:

   1: int i = 5;

i is a Lvalue because it has a name and we can use it after its declaration, 5 is a Rvalue because it’s just a temporary integer object, we can’t reference it in anywhere in the program normally.

In previous version of Visual C++, we have no language features to distinguish Rvalues from Lvalues when we pass parameters to the function. Now in Visual C++ 2010, we can use Rvalue Reference Declarator(&&) in the definition of a function to identify the Rvalue parameter. We can define an overloading function as below:

   1: void Func1(const int &)
  2: {
  3:     cout<<"Lvalue parameter come in."<<endl;
  4: }
  5: 
  6: void Func1(int &&)
  7: {
  8:     cout<<"Rvalue parameter come in."<<endl;
  9: }
 10: 

When we pass Rvalues to this function, the compiler will call the version of Rvalue Reference:

   1: int i = 0;
  2: Func1(i);
  3: Func1(0);

The output would be:

Lvalue parameter come in.

Rvalue parameter come in.

Maybe you will say “So what is the benefit of this feature?” Let’s proceed further to see the Move Semantics.

Move Semantics -- resource moving instead of re-creating

As we know, an object which contains resource cannot be simply shallow copied, instead we define a copy constructor for allocating other resource to the new objects. For example:

   1: class MemoryBlock
  2: {
  3: public:
  4: // Simple constructor that initializes the resource.
  5:     explicit MemoryBlock(size_t length)
  6:       : _length(length)
  7:       , _data(new int[length])
  8:    {
  9:         std::cout << "In MemoryBlock(size_t). length = "
 10:                 << _length << "." << std::endl;
 11:     }
 12: 
 13:     // Destructor.
 14:     ~MemoryBlock()
 15:     {
 16:         std::cout << "In ~MemoryBlock(). length = "
 17:                 << _length << ".";
 18:       
 19:         if (_data != NULL)
 20:         {
 21:             std::cout << " Deleting resource.";
 22:             // Delete the resource.
 23:             delete[] _data;
 24:         }
 25: 
 26:         std::cout << std::endl;
 27: }
 28: 
 29: // Copy constructor.
 30:     MemoryBlock(const MemoryBlock& other)
 31:       : _length(other._length)
 32:       , _data(new int[other._length])
 33:     {
 34:         std::cout << "In MemoryBlock(const MemoryBlock&). length = " 
 35:                 << other._length << ". Copying resource." << std::endl;
 36: 
 37:         std::copy(other._data, other._data + _length, _data);
 38:    }
 39: private:
 40:    size_t _length; // The length of the resource.
 41:     int* _data; // The resource.
 42: };
 43: 

Now let’s define a function which accepts a MemoryBlock object as the parameter. It uses this parameter to create another MemoryBlock object for some purpose:

   1: void Func2(const MemoryBlock &m)
  2: {
  3:     MemoryBlock mm(m);
  4: }

Then call it:

MemoryBlock m(5);

Func2(m);

It has no problem. The copy constructor will be called to allocate another block of memory for the new object, that’s what we expected. Now let’s consider another scenario:

Func2(MemoryBlock(10));

In this example we are passing a Rvalue parameter. It will also call the copy constructor to allocate a new block of memory for the new object, but we know the source object is a temporary object and we won’t reference it in any other places of the program. Now you may want to do a shallow copy since the memory re-allocating is unnecessary. Moving the resource from the temporary object to the new object is a better solution. In this way, we can improve the performance of our program from reducing the expensive memory allocating. This is called Move Semantics, and it can be seen as “do the shallow copy when the source object is Rvalue and do the deep copy when the source object is Lvalue”.

Therefore, we can define a Move Constructor which takes Rvalues as the copy source:

   1: // Move constructor.
  2: MemoryBlock(MemoryBlock&& other)
  3:    : _data(NULL)
  4:    , _length(0)
  5: {
  6:    std::cout << "In MemoryBlock(MemoryBlock&&). length = " 
  7:            << other._length << ". Moving resource." << std::endl;
  8: 
  9:     // Copy the data pointer and its length from the 
 10:    // source object.
 11:    _data = other._data;
 12:    _length = other._length;
 13: 
 14:     // Release the data pointer from the source object so that
 15:     // the destructor does not free the memory multiple times.
 16:   other._data = NULL;
 17:  other._length = 0;
 18: }
 19: 

Then we can define an Rvalue reference version of function Func2():

   1: void Func2(MemoryBlock &&m)
  2: {
  3:     MemoryBlock mm(std::forward<MemoryBlock>(m));
  4: }
  5: 

In this overloading version of function Func2(), we use std::forward to pass Rvalues to the move constructor of MemoryBlock. It’s necessary to use std::forward to forward the parameter of a function to another function, so the compiler can know which version of constructor to call. This is known as Perfect Forward, which is another advance of Rvalue Reference Declarator (&&). Today I will not introduce the Perfect Forward. For more detailed introduction, please see Rvalue References: C++ 0x Features in VC10, Part 2.

Okay, let’s continue the testing:

   1: MemoryBlock m(5);
  2: 
  3: Func2(m);
  4: 
  5: Func2(MemoryBlock(10));

The next call of Func2() will use the Rvalue reference version since the compiler knows we are passing Rvalues, and it will call the move constructor to simply move the memory resource from the temporary object to the new object. The sample will show us these outputs:

   1: In MemoryBlock(size_t). length = 5.
  2: 
  3: In MemoryBlock(const MemoryBlock&). length = 5. Copying resource.
  4: 
  5: In ~MemoryBlock(). length = 5. Deleting resource.
  6: 
  7: In MemoryBlock(size_t). length = 10.
  8: 
  9: In MemoryBlock(MemoryBlock&&). length = 10. Moving resource.
 10: 
 11: In ~MemoryBlock(). length = 10. Deleting resource.
 12: 
 13: In ~MemoryBlock(). length = 0.

STL Support

In the previous testing, I’m doing the same thing as the STL containers’ push_back() method did. In Visual C++ 2010, the STL containers have been improved with the help of Rvalue Reference. push_back() and other methods have their corresponding overloading version for the Rvalue Reference. Let’s have a look at this sample:

   1: vector<MemoryBlock> v;
  2: 
  3: v.reserve(2);
  4: 
  5: v.push_back(MemoryBlock(5));
  6: 
  7: v.push_back(MemoryBlock(10));

In the past, we cannot define a move constructor for MemoryBlock and the push_back() method of vector also has no overloading version for Rvalue Reference. The above sample will generate outputs below:

   1: In MemoryBlock(size_t). length = 5.
  2: 
  3: In MemoryBlock(const MemoryBlock&). length = 5. Copying resource.
  4: 
  5: In ~MemoryBlock(). length = 5. Deleting resource.
  6: 
  7: In MemoryBlock(size_t). length = 10.
  8: 
  9: In MemoryBlock(const MemoryBlock&). length = 10. Copying resource.
 10: 
 11: In ~MemoryBlock(). length = 10. Deleting resource.
 12: 

Now we have the outputs below:

   1: In MemoryBlock(size_t). length = 5.
  2: 
  3: In MemoryBlock(MemoryBlock&&). length = 5. Moving resource.
  4: 
  5: In ~MemoryBlock(). length = 0.
  6: 
  7: In MemoryBlock(size_t). length = 10.
  8: 
  9: In MemoryBlock(MemoryBlock&&). length = 10. Moving resource.
 10: 
 11: In ~MemoryBlock(). length = 0.

That’s all I will share today. Rvalue Reference is a very cool feature. It can increase the performance of our program. I hope this blog can help you understand the main advantage of Rvalue Reference easliy. For more information about Rvalue Reference Declarator (&&), please refer to the MSDN document:

https://msdn.microsoft.com/en-us/library/dd293668(VS.100).aspx

Please let me know if you have any comments about this article.

Reference:

MSDN: Rvalue Reference Declarator: &&

https://msdn.microsoft.com/en-us/library/dd293668(VS.100).aspx

 

By Wesley Yao, MSDN support team, 4 star contributor on forum

If you want more information shared by our team, please follow us @ Twitter