rvalue references and move semantics.

  Rvalue reference


C++11 introduces a new category of reference types called rvalue references which bind only to rvalues and are declared with two ampersands rather than one.

Int&& rvalueRef = 99;


The primary reason for adding rvalue reference is move semantics. Unlike traditional copying, moving means that a target object pilfers the resources of the source object, leaving the source in an “empty” state. In certain cases where making a copy of an object is both expensive and unnecessary, a move operation can be use instead.

We cannot bind rvalue references to lvalues.


Int j = 99;
Int&& k = j;

We will get an error something like this :

Error: 'initializing' : cannot convert from 'int' to 'int &&'
                 You cannot bind an lvalue to an rvalue reference



An expression is an rvalue if it results in an temporary object as shown below


Int getVal ()
{
            Int a = 10;
            Return a;
}

Int main()
{
            Cout<<getVal();
            Return 0;
}

getVal() is a rvalue. Note that the value returned is not a reference to a, but it’s just temporary value.

In C++98, lvalue reference can bind to temporary object only if it was const.

Const int& value = getVal() ; // Fine
Int& value = getVal(); // Error

Rvalue references are perfect for detecting if a value is temporary object or not.

Const int&& value = getVal() ; // Fine
Int&& value = getVal(); // Fine


Some more examples.

Void displayValue(const int& val)
{
            Cout<<val;
}

Void displayValue(int && val)
{
            Cout<<val;
}

The displayValue() function taking const lvalue reference will accept any argument whether it can be lvalue or rvalue. The second function taking rvalue will accept only temporary/rvalue as argument.

            #include<iostream>

Using namespace std;

Void displayValue(const int& val)
{
            Cout<<”Lvalue - ”<<val;
}

Void displayValue(int && val)
{
            Cout<<”RValue - ”<<val;
}


Int returnVal()
{
            Int aa = 20;
            Return aa;
}

Int main()
{
            Int a = 10;
            displayValue(a);
            displayValue(returnValue());
            return 0;
}

g++ -std=c++11 rvalueReference.cpp

Execute and get the below output.

Lvalue – 10
RValue - 20




 Move Semantics


To move an object means to transfer ownership of some resource it manages to another object.

Now we have a way to determine if a reference variable refers to temporary object or permanent object.
The main usage of rvalue references is to create a move constructor and move assignment operator.

A move constructor, like a copy constructor, takes an instance of an object as its argument and creates a new instance from original object. However, the move constructor can avoid memory reallocation because we know it has been provided a temporary object, so rather than copy the fields of the object, we will move them.

In other words, the rvalue references and move semantics allows us to avoid unnecessary copies when working with temporary objects.

The rvalues are typically temporary, and they can be modified. If we know that our function parameter is an rvalue, we can use it as a temporary storage, or takes it contents without affecting the outpt of our program. This means we can just move the contents rather than copy the contents of an rvalues parameter.

What does it mean to move a field of the object ? 
If the field is a primitive type, like int, we just copy it. It gets more interesting if the field is a pointer : here, rather than allocate and initialize new memory, we can simply steal the pointer and null out the pointer in the temporary object ! We know that temporary object will no longer be needed.

A move constructor looks like this :

MyClass::MyClass(MyClass && obj) ;


It does’nt allocate new resources, instead it pilfers “obj” resources and then sets “obj” to its default constructed state.

Suppose you are designing a MemoryModel class that represents a memory buffer:

Class MemoryModel
{
            Size_t size;
            Char* buf;
            Public:
                        Explicit MemoryModel(int sz = 32):size(sz),buf(new char[sz])
                        {
                        }
                       
                        ~MemoryModel()
                        {
                                    Delete buf[];
                        }
                       
                        // Copy Constructor
                        MemoryModel(const MemoryModel& obj);

                        // Assignment Operator
                        MemoryModel& operator=(const MemoryModel& obj);
                       

}
// Move Constructor definition
MemoryModel(MemoryModel&& obj) : size(0),buf(nullptr)
{
            // Pilfer’s “obj” resource
            Size = obj.size;
            Buf = obj.buf;
           
            // Reset “obj”
            Obj.size = 0;
            Obj.buf = nullptr;
}



// Move Assignment operator :
MemoryModel& MemoryModel::operator=(MemoryModel&& obj);

A move assignment operator is similar to copy constructor except that before pilfering the source object, it releases any resources that its main object may own.
a.     Release any resource that *this currently owns.
b.     Pilfer “obj” resource
c.     Set “obj” to a default state
d.     Return *this

Definition of MemoryModel move Assignment Operator:

MemoryModel& MemoryModel::operator=(MemoryModel&& obj)
{
            If(this != &obj)
            {
                        Delete []buf;
                        Size = 0;
                       
                        // Pilfer obj’s resource
                        Size = obj.size;
                        Buf = obj.buf;

                        //Reset obj
                        Obj.size = 0;
                        Obj.buf = nullptr;

            }
            Return *this;


}

The copy constructor is doing both allocate memory and copy every value from array one by one, and it may hit the performance of our code.

On the other hand, the move constructor is actually less work than the copy constructor. Note that the following two facts from the move constructor.


  • The parameter is a non-const rvalue reference. - - If we had taken a const-rvalue reference, we were not able to set obj.buf to nullptr.
  • obj.buf is set to nullptr - - The reason we are setting the ptr to nullptr is the destructor. When the temporary object goes out of scope, its destructor will be invoked, and it will free obj.buf. The same obj.buf that we copied, if we dont set obj.buf to nullptr, the move would not be an move. it would just be a copy that introduces a crash later on once we start using freed memory.

Comments

Popular posts from this blog

C++ Guidelines for Multithreaded System

Signalling System #7(SS7) Protocol.

std::shared_ptr