Introduction
Move semantics in C++ is used to move resources around in an optimal way by avoiding unnecessary copies of temporary objects. C++11 defines two new functions for move semantics : move constructor, and move assignment operator. Copy constructor (and assignment) make a copy of one object to another, while move constructor (and assignment) move ownership of the resources from one object to another. It is much less expensive than making a copy. Defining a move constructor and move assignment work analogously to their copy counterparts. Copy flavours of these functions take a const lvalue reference parameter, move flavours of these functions use non-const rvalue reference parameters.
rvalue, lvalue, and &&
Move semantics work on rvalue reference. Concepts of rvalues and lvalues are
- An lvalue is an expression whose address can be taken, a locator value. Anything you can make assignments to is an lvalue.
- An rvalue is an unnamed value that exists only during the evaluation of an expression. In other words an expression is an rvalue if it results in a temporary object.
- && operator is new in C++11, and is like the reference operator (&). But & operator can be used on lvalues, the && operator can only be used on rvalues.
To illustrate this, consider the following examples:
int a = 1; // here, a is an lvalue (and the '1' is an rvalue) // getRef() is an lvalue - returns a reference to a global variable, so it's returning a value that is stored in a permanent location. int x; int& getRef () { return x; } getRef() = 4; // Returns a local variable, constructed inside the function. It is returning an rvalue string getName() { string s = "Hello world"; return s; } // getName() returns an rvalue, assigning getName()'s result to an rvalue reference is possible string&& name1 = getName(); // getName() can also be assigned to a value object, without any copying of string data being required string name2 = getName();
Need of move semantic
When doubleValues
in the below example is called, it constructs a vector, new_values, and fills it up. When this function hit the return statement, there could be up to two copies. One into a temporary object to be returned, and a second when the vector assignment operator runs on the line v = doubleValues(v)
;. The first copy may be optimized by compiler automatically. But second copy requires a new memory allocation and another iteration over the entire vector.
Since the object returned from doubleValues is a temporary value that’s no longer needed. It is possible to avoid copy and just pilfer the pointer inside the temporary vector and keep it in v. In C++11, move semantics can be used to move and avoid the copy.
Move semantics allows to avoid unnecessary copies when working with temporary objects, and whose resources can safely be taken from that temporary object and used by another. Move semantics relies on a new feature of C++11, called rvalue references.
#include <iostream> using namespace std; vector<int> doubleValues (const vector<int>& v) { vector<int> new_values; new_values.reserve(v.size()); for (auto itr = v.begin(), end_itr = v.end(); itr != end_itr; ++itr ) { new_values.push_back( 2 * *itr ); } return new_values; } int main() { vector<int> v; for ( int i = 0; i < 100; i++ ) { v.push_back( i ); } v = doubleValues( v ); }
Detecting temporary objects with rvalue references
An rvalue reference is a reference that will bind only to a temporary object. t. Rvalue references use the && syntax instead of just &, and can be const and non-const.
const string&& name = getName(); string&& name = getName();
In the below example, first printReference() function taking a const lvalue reference. It will accept any argument, whether it is an lvalue or an rvalue. Second printReference() accepts an rvalue reference, it will be given all values except mutable rvalue-references.
printReference (const String& str) { cout << str; } printReference (String&& str) { cout << str; } string name("IN"); // calls the first printReference function, taking an lvalue reference printReference(name); // calls the second printReference function, taking a mutable rvalue reference printReference(getName());
Special Member Functions
In old C++, there were four special member functions. Now with C++11’s two move semantics functions, there’s six:
- Default constructor
- Destructor
- The two copy special member functions
- Copy constructor
- Copy assignment operator
- The two move special member functions
- Move constructor
- Move assignment operator
In C++ when you declare any constructor, the compiler will no longer generate the default constructor. Same is true for move constructor. Adding a move constructor to a class will require you to declare and define your own default constructor. On the other hand, declaring a move constructor does not prevent the compiler from providing an implicitly generated copy constructor. Also declaring a move assignment operator does not inhibit the creation of a standard assignment operator.
Supporting move semantics
To support moving, you class needs:
- Move constructor of the form C::C(C&& other)
- Move assignment operator of the form C& C::operator=(C&& other)
A move constructor, is like a copy constructor. It takes an instance of an object as its argument and creates a new instance based on the original object. However, move constructor avoid memory reallocation because it has been provided a temporary object. So rather than copy the fields of the object, it will move them. If the field is a primitive type, like int, we just copy it. If the field is a pointer, rather than allocate and initialize new memory, it simply steal the pointer and null out the pointer in the temporary object. Consider below example
class ArrayWrapper { public: // Constructor ArrayWrapper (int n) : _p_vals(new int[n]) , _size(n) {} // Move constructor ArrayWrapper (ArrayWrapper&& other) : _p_vals( other._p_vals ) , _size( other._size ) { other._p_vals = NULL; other._size = 0; } // Copy constructor ArrayWrapper (const ArrayWrapper& other) : _p_vals( new int[other._size] ) , _size(other._size) { for ( int i = 0; i < _size; ++i ) { _p_vals[ i ] = other._p_vals[ i ]; } } ~ArrayWrapper () { delete [] _p_vals; } private: int *_p_vals; int _size; };
In move constructor, other._p_vals is set to NULL . The reason is the destructor, when temporary object goes out of scope, its destructor will run. When its destructor runs, it will free _p_vals which was just moved. If we don’t set other._p_vals to NULL, the move would not really be a move, it would just be a copy that introduces a crash later on once we start using freed memory.
Another example which illustrates how to implement move semantic. In this example – need to implement move semantics because of the pointer used in the class and also have a bunch of other class members which can all be moved safely, then to move them within the move constructor + move assignment operator you can simply use std::move, i.e. if MyClass above also had a member string myName.
class MyClass { int* buffer = nullptr; string myName; public: // Move constructor MyClass(MyClass&& other) { buffer = other.buffer; other.buffer = nullptr; myName = std::move(other.myName); } // Move assignment operator MyClass& MyClass::operator=(MyClass&& other) { if(this != &other) { if(buffer) { delete buffer; } // Steal data from the other object coming in as an rvalue reference, buffer = other.buffer; other.buffer = nullptr; myName = std::move(other.myName); } return *this; } ~MyClass { if(buffer) { delete buffer; } } };