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 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
Post a Comment