std::move in Utility in C++ | Move Semantics, Move Constructors and Move Assignment Operators
In C++ there are two types of references-
- lvalue reference:
- An lvalue is an expression that will appear on the left-hand side or on the right-hand side of an assignment.
- Simply, a variable or object that has a name and memory address.
- It uses one ampersand (&).
- rvalue reference:
- An rvalue is an expression that will appear only on the right-hand side of an assignment.
- A variable or object has only a memory address (temporary objects).
- It uses two ampersands (&&).
Move Constructor And Semantics:
The move constructor was introduced in C++11. The need or purpose of a move constructor is to steal or move as many resources as it can from the source (original) object, as fast as possible, because the source does not need to have a meaningful value anymore, and/or because it is going to be destroyed in a moment anyway. So that one can avoid unnecessarily creating copies of an object and make efficient use of the resources
While one can steal the resources, but one must leave the source (original) object in a valid state where it can be correctly destroyed.
Move constructors typically “steal” the resource of the source (original) object rather than making several copies of them, and leaves the source object in a “valid but unspecified state”.
The copy constructor uses the lvalue references which are marked with one ampersand (&) while the move constructor uses the rvalue references are marked with two ampersands (&&).
std::move() is a function used to convert an lvalue reference into the rvalue reference. Used to move the resources from a source object i.e. for efficient transfer of resources from one object to another.
std::move() is defined in the <utility> header.
- template< class T >
typename std::remove_reference<T>::type&& move(T&& t) noexcept; (since C++11)(until C++14)
- template< class T >
constexpr std::remove_reference_t<T>&& move(T&& t) noexcept (since C++14)
Example: Below is the C++ program to show what happens without using move semantics i.e. before C++11.
Hello HelloHello Hello
Assuming the program is compiled and executed using a compiler that doesn’t support move semantics. In the main() function,
1. std::vector<std::string> vecString;- An empty vector is created with no elements in it.
2. vecString = createAndInsert();- The createAndInsert() function is called.
3. In createAndInsert() function-
- std::vector<std::string> vec;- Another new empty vector named as vec is created.
- vec.reserve(3);- Reserving the size of 3 elements.
- std::string str(“Hello”);- A string named as str initialized with a “Hello”.
- vec.push_back( str );- A string is passed by value into the vector vec. Therefore a (deep) copy of str will be created and inserted into the vec by calling a copy constructor of the String class.
- vec.push_back( str + str );- This is a three-stage process-
- A temporary object will be created (str + str) with its own separate memory.
- This temporary object is inserted into vector vec which is passed by value again means that a (deep) copy of the temporary string object will be created.
- As of now, the temporary object is no longer needed hence it will be destroyed.
Note: Here, we unnecessarily allocate & deallocate the memory of the temporary string object. which can be optimized (improved) further just by moving the data from the source object.
- vec.push_back( str );- The same process as of Line no. 5 will be carried out. Remember at this point the str string object will be last used.
- return vec;- This is at the end of the createAndInsert() function-
- Firstly, the string object str will be destroyed because the scope is left where it is declared.
- Secondly, a local vector of string i.e vec is returned. As the return type of the function is not by a reference. Hence, a deep copy of the whole vector will be created by allocating at a separate memory location and then destroys the local vec object because the scope is left where it is declared.
- Finally, the copy of the vector of strings will be returned to the caller main() function.
- At the last, after returning to the caller main() function, simply printing the elements of the local vecString vector.
Example: Below is the C++ program to implement the above concept using move semantics i.e. since C++11 and later.
Hello HelloHello Hello
Here, in order to use the move semantics. The compiler must support the C++11 standards or above. The story of execution for the main() function and createAndInsert() function remains the same till the line vec.push_back( str );
A question may arise why the temporary object is not moved to vector vec using std::move(). The reason behind it is the push_back() method of the vector. Since C++11 the push_back() method has been provided with its new overloaded version.
- constexpr void push_back(const T& value); (since C++20)
- void push_back(T&& value); (since C++11) (until C++20)
- void push_back(const T& value); (until C++20)
- constexpr void push_back(T&& value); (since C++20)
- vec.push_back(str + str);-
- A temporary object will be created (str + str) with its own separate memory and will make a call to overloaded push_back() method (version 2nd or 4th depends on the version of C++) which will steal (or moved) the data from the temporary source object (str + str) to the vector vec as it is no longer required.
- After performing the move the temporary object gets destroyed. Thus rather than calling the copy constructor (copy semantics), it is optimized just by copying the size of the string and manipulating pointers to the memory of the data.
- Here, the important point to note is that we make use of the memory which will soon no longer owns its memory. In another word, we somehow optimized it. That’s all because of the rvalue reference and move semantics.
- vec.push_back(std::move(str));- Here the compiler is explicitly hinted that “object is no longer needed” named as str (lvalue reference) with the help of std::move() function by converting the lvalue reference into rvalue reference and the resource of the str will be moved to the vector. Then the state of str becomes a “valid but unspecified state”. This doesn’t matter to us because for the last time we are going to use and soon be destroyed in a moment anyway.
- Lastly, return the local vector of string called vec to its caller.
- In the end, returned to the caller main() function, and simply printing the elements of the local vecString vector.
A question may arise while returning the vec object to its caller. As it is not required anymore and also a whole temporary object of a vector is going to be created and also local vector vec will be destroyed, then why std::move() is not used to steal the value and return it.
Its answer is simple and obvious, there is optimization at the compiler level known as (Named) Return Value Object, more popularly known as RVO.
Some Fallback Of Move Semantics:
- Calling a std::move() on a const object usually has no effect.
- It doesn’t make any sense to steal or move the resources of a const object.
- See constObjectCallFunc() function in the below program
- Copy semantics is used as a fallback for move semantics if and only if copy semantics is supported.
- See baz() function in the below program
- If there is no implementation taking the rvalue reference as an argument the ordinary const lvalue reference will be used.
- See baz() function in the below program
- If a function or method is missing with rvalue reference as argument & const lvalue reference as an argument. Then the compile-time error will be generated.
- See bar() function in the below program
Note: The foo() function have all necessary types of arguments.
Below is the C++ program to implement all the above concepts-
foo(std::string&& str) : Hello foo(std::string& str) : Good Bye! foo(std::string&& str) : Good Bye! using std::move() baz(const std::string& str) : This is temporary string object baz(const std::string& str) : This is temporary string object using std::move()
- Move semantics allows us to optimize the copying of objects, where we do not need the worth. It is often used implicitly (for unnamed temporary objects or local return values) or explicitly with std::move().
- std::move() means “no longer need this value”.
- An object marked with std::move() is never partially destroyed. i.e. The destructor will be called to destroy the object properly.