1. Prerequisites : Someone initially needs to understand lvalue and rvalue concepts for understanding move semantics. Making the assumption you already know about rvalues, rvalue reference has been introduced in C++11 for mainly two things :
a) Move semantics
b) Perfect forwarding ( targeting template functions )
Basically rvalues references are able to store rvalues. For example function overloading with rvalue references is also possible :
2. Move semantics introduction & Rule of Five : In C++03, there was no way to tell that an object is temporary. So therefore , you were not able to avoid copy constructor calls which implement costly deep copy operations. Or alternatively , it was not possible to change between a deep copy where you make costly allocations and a shallow copy , where you just swap pointers.
However in C++11 by using rvalue references , you can implement move constructors and move assignment operators that will be called whenever an rvalue passed. And this allows you to implement cheaper shallow copy whenever it is necessary. Therefore as used in the below , now Rule of 3 can be extended to Rule of 5 :
In the code snippet above , the move constructor is rule_of_five(rule_of_five&& other) and the move assignment operator is rule_of_five& operator=(rule_of_five&& other).
So we can say that now the “Rule of Three” ( http://en.wikipedia.org/wiki/Rule_of_three_(C%2B%2B_programming) ) can be extended to the “Rule of Five”.
Also notice that in the move constructor , the other object`s pointer is set to NULL. That is because the other rvalue object will be destroyed very soon since it is an rvalue. Therefore if the destructor is deleting that pointer, this move construction will be pointless since the resource will be deleted. Therefore we null the pointer member of the other side. Note that this behaviour is like auto_ptr smart point class which is deprecated in C++11.
3. Relationship between std::auto_ptr and move semantics : If you looked at the example above , in a move operation we do two things :
1) Moving resources / Shallow copy by copying the resource from the rvalue object/ other side
2) Invalidating the resource used in the rvalue object/other side in order to avoid other objects`s destructror`s delete
You can notice these 2 features also in std::auto_ptr. Therefore move behaviour can be mimiced by using std::auto_ptr. But of course that is not going to work with STL classes. Therefore move semantics gives a natural , language-supported way of moving object with a shallow copy. Because of also that std::auto_ptr is deprecated and std::unique_ptr are supposed to be used.
4. Move semantics & STL containers : In C++11 , if you can declare your classes to be “movable” which means the implementation of both, STL containers will be calling move constructors except from the copy constructors.
Considering the implemented class rule_of_five :
Also note that if a reallocation happens in an std::vector, then the move constructor should use the exception specification : noexcept(true).
5. std::move & std::move_if_no_except : This C++ standard function is declared inside and it helps us to convert lvalue to an rvalue. This is quite useful when there is a “movable” class member inside the class such as std::string. Therefore this one helps us to use move semantics not only for our class, but use it for class members.
On the other hand std::move_if_no_except will convert an lvalue to an rvalue , if the move constructor uses the “no” exception specification : noexcept (true)
6. Return Value Optimisation : RVO is compiler optimisation. For instance when you return a local object , the compiler can build the object directly at the end of function so avoids one copy. In GCC, you can use -fno-elide-constructors which will disable return value optimisation. This is useful in order to practise move constructors.
The example code below demonstrates RVO in C++. We have a function called Foo which returns a locally created BigObject instance. In that example , copy constructor will not be called , but the object will be created directly in the place that calls Foo. Also notice that RVO won`t be there when a branch / if case added to Foo function :
7. Compiler generated functions : C++11 changes the rules about the compiler generated functions :
1. Default constructor : Generated if 1,2,5 are not declared by user
2. Copy constructor : Generated if 2,3,4,5,6 are not declared by user
3. Copy assignment operator : Generated if 2,3,4,5,6 are not declared by user
4. Destructor : Generated if not declared by user
5. Move constructor : Generated if 2,3,4,5,6 are not declared by user
6. Move assignment operator : Generated if 2,3,4,5,6 are not declared by user
8) Canonical class and experiments with STL : Below you can see an implementation of a class that implements all constructor including move ons and also destructor. It also has helper methods such as swap( for copy-swap ) , deep copy , shallow copy and static object counter. The test code invokes different constructors and eventually it interacts with std::vector in different ways ( push_back/emplace_back and via lvalue and rvalue ) :
And I observed from the output from both MSVC and GCC ( with move ctor with noexcept and move ctor without noexcept ). As a quick summarisation, MSVC seems it is using the move constructor in almost all cases it can use whether it is noexcept or not , on the other hand GCC seems more C++ standards friendly as if you don`t make your move constructor noexcept , the copy constructor will be called in some instances. You can see 3 different set of outputs :
- MSVC ( noexcept does not make a difference )
- GCC ( with noexcept move constructor )
- GCC (without noexcept move constructor )
9) Perfect forwarding and implementing make_unique for C++11 : Perfect forwarding helps us to retain lvalue/rvalue property of a template function argument. Without using perfect forwarding, when you pass an rvalue as template function argument, it will be deduced to type value. The example below is a simple implementation of make_unique functionality for C++11. ( make_unique added in C++14 ). If you don`t use std::forward , the same constructor will be called. But with std::forward, different constructors will be called :