In this article we will discuss the use of rvalue references in C++11 move semantics.
Problem of Temporary Objects
The Idea behind this move semantics is to reduce the load of these temporary objects on memory. Every time we return a object from a function then a temporary object is created, which eventually get copied. In then end we create 2 copies of an object whereas, we need only one. Let’s understand by an example,
Suppose we have a Container class that contains a integer pointer as member variable,
class Container { int * m_Data; public: Container() { //Allocate an array of 20 int on heap m_Data = new int[20]; std::cout << "Constructor: Allocation 20 int" << std::endl; } ~Container() { if (m_Data) { delete[] m_Data; m_Data = NULL; } } Container(const Container & obj) { //Allocate an array of 20 int on heap m_Data = new int[20]; //Copy the data from passed object for (int i = 0; i < 20; i++) m_Data[i] = obj.m_Data[i]; std::cout << "Copy Constructor: Allocation 20 int" << std::endl; } };
When we create an object of Container class, then its default constructor internally allocates an array of 20 int on heap and assign it to its member variable.
Similarly, Container class’s copy constructor allocates an array of 20 int on heap, then copy the contents of passed objects array into it and then assign it to its member variable.
Generally, we use Factory classes to create object of our classes. On similar lines, let’s create a simple function that creates an object of Class Container and returns i.e.
// Create am object of Container and return Container getContainer() { Container obj; return obj; }
Now in main function we created a vector of Container type and inserted an object returned by getContainer() function i.e.
int main() { // Create a vector of Container Type std::vector<Container> vecOfContainers; //Add object returned by function into the vector vecOfContainers.push_back(getContainer()); return 0; }
Now, at last there is one object in the vector  vecOfContainers. But we actually created 2 objects for it because getContainer() function returned a temporary object which got copied into a new object and then destructed. This 2nd object got inserted in vector. So, 2 objects of class Container are created at following step in above code,
Best Resources to Learn C++:
- One inside the getContainer() function using Container class’s default constructor
- Second while adding object in vector using Container class’ copy constructor.
To create each of these 2 objects it allocated an array of 20 int on heap 2 times and at last only one was used. So, its clearly a wastage of resources and effort.
How to solve this problem of resource and effort wastage due to temporary objects? Is there a way to move the 1st object instead of creating 2nd one and copying contents to it?
Answer is yes. This is where move semantics and rvalue references comes into picture.
Solving Problem of Temporary Objects using rvalue references & Move Constructor
The getContainer() function here is a rvalue, so it can be referred by a rvalue reference. Also, using rvalue reference we can also overload functions. This time, we will overload the Constructor of class Container and this new Constructor will be called move constructor.
Move Constructor
Move constructor takes a rvalue reference as an argument and that makes it overloaded because Copy Constructor takes the const lvalue reference as an argument. In Move constructor we just move the member variables of passed object into the new object’s member variables, instead of allocating new memory for them.
Let’s see the move constructor for class Container i.e.
Container(Container && obj) { // Just copy the pointer m_Data = obj.m_Data; // Set the passed object's member to NULL obj.m_Data = NULL; std::cout<<"Move Constructor"<<std::endl; }
In the move constructor, we just copied the pointer. Now member variable m_Data points to the same memory on heap. Then we set the m_Data of passed object to NULL. So, we didn’t allocated any memory on heap in move constructor, we just shifted the control of memory.
Now if we create the vector  of class container and push a object returned from getContainer() into it. Then a new object will created from this temporary object but as getContainer() is a rvalue, so Move Constructor of this new Container class’s object will be called and in that memory will be just shifted. So, actually on heap we will create only one array of integers.
[showads ad=inside_post]
Similar to Move Constructor we can have Move Assignment operator that will just shift the content. Checkout the complete example as follows,
#include <iostream> #include <vector> class Container { int * m_Data; public: Container() { //Allocate an array of 20 int on heap m_Data = new int[20]; std::cout << "Constructor: Allocation 20 int" << std::endl; } ~Container() { if (m_Data) { delete[] m_Data; m_Data = NULL; } } //Copy Constructor Container(const Container & obj) { //Allocate an array of 20 int on heap m_Data = new int[20]; //Copy the data from passed object for (int i = 0; i < 20; i++) m_Data[i] = obj.m_Data[i]; std::cout << "Copy Constructor: Allocation 20 int" << std::endl; } //Assignment Operator Container & operator=(const Container & obj) { if(this != &obj) { //Allocate an array of 20 int on heap m_Data = new int[20]; //Copy the data from passed object for (int i = 0; i < 20; i++) m_Data[i] = obj.m_Data[i]; std::cout << "Assigment Operator: Allocation 20 int" << std::endl; } } // Move Constructor Container(Container && obj) { // Just copy the pointer m_Data = obj.m_Data; // Set the passed object's member to NULL obj.m_Data = NULL; std::cout<<"Move Constructor"<<std::endl; } // Move Assignment Operator Container& operator=(Container && obj) { if(this != &obj) { // Just copy the pointer m_Data = obj.m_Data; // Set the passed object's member to NULL obj.m_Data = NULL; std::cout<<"Move Assignment Operator"<<std::endl; } } }; // Create am object of Container and return Container getContainer() { Container obj; return obj; } int main() { // Create a vector of Container Type std::vector<Container> vecOfContainers; //Add object returned by function into the vector vecOfContainers.push_back(getContainer()); Container obj; obj = getContainer(); return 0; }
Output:
Constructor: Allocation 20 int Move Constructor Constructor: Allocation 20 int Constructor: Allocation 20 int Move Assignment Operator
In the above example, Move constructor of class Container will be called because getContainer() returns a rvalue and Container class has a overloaded version of Constructor that accepts rvalue in rvalue reference. Inside this Move constructor memory is just shifted.
Similarly in following lines,
Container obj; obj = getContainer(); // Move Assignment will be called
Move Assignment Operator was called instead of assignment operator and memory just got shifted.
Confusing topic explained in a very nice way…Great tutorial.
Good work, great contents. recommended for every mid to senior level C++ professionals.
Like the way explained this topic…Nice work.
Great tutorial! You jest forgot to return *this in overloaded operators
Keep doing great work! Maybe C++17? 🙂
Amazing article, Explained in simple and neat way