Open In App

std::move in Utility in C++ | Move Semantics, Move Constructors and Move Assignment Operators

Last Updated : 11 Feb, 2022
Improve
Improve
Like Article
Like
Save
Share
Report

Prerequisites:

  1. lvalue reference
  2. rvalue reference
  3. Copy Semantics (Copy Constructor)

References:

In C++ there are two types of references-

  1. 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 (&).
  2. 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.

Syntax:

  • 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.

C++14




// C++ program to implement
// the above approach
 
// for std::string
#include <string>
 
// for std::cout
#include <iostream>
 
// for EXIT_SUCCESS macro
#include <cstdlib>
 
// for std::vector
#include <vector>
 
// for std::move()
#include <utility>
 
// Declaration
std::vector<std::string> createAndInsert();
 
// Driver code
int main()
{
    // Constructing an empty vector
    // of strings
    std::vector<std::string> vecString;
 
    // calling createAndInsert() and
    // initializing the local vecString
    // object
    vecString = createAndInsert();
 
    // Printing content of the vector
    for (const auto& s : vecString) {
        std::cout << s << '\n';
    }
 
    return EXIT_SUCCESS;
}
 
// Definition
std::vector<std::string> createAndInsert()
{
    // constructing a vector of
    // strings with an size of
    // 3 elements
    std::vector<std::string> vec;
    vec.reserve(3);
 
    // constructing & initializing
    // a string with "Hello"
    std::string str("Hello");
 
    // Inserting a copy of string
    // object
    vec.push_back(str);
 
    // Inserting a copy of an
    // temporary string object
    vec.push_back(str + str);
 
    // Again inserting a copy of
    // string object
    vec.push_back(std::move(str));
 
    // Finally, returning the local
    // vector
    return vec;
}


Output

Hello
HelloHello
Hello

Explanation:

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-
    1. A temporary object will be created (str + str) with its own separate memory.
    2. 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.
    3. 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. 

C++14




// C++ program to implement
// the above approach
 
// for std::string
#include <string>
 
// for std::cout
#include <iostream>
 
// for EXIT_SUCCESS macro
#include <cstdlib>
 
// for std::vector
#include <vector>
 
// for std::move()
#include <utility>
 
// Declaration
std::vector<std::string> createAndInsert();
 
// Driver code
int main()
{
    // Constructing an empty vector
    // of strings
    std::vector<std::string> vecString;
 
    // calling createAndInsert() and
    // initializing the local vecString
    // object
    vecString = createAndInsert();
 
    // Printing content of the vector
    for (const auto& s : vecString) {
        std::cout << s << '\n';
    }
 
    return EXIT_SUCCESS;
}
 
// Definition
std::vector<std::string> createAndInsert()
{
    // constructing a vector of
    // strings with an size of
    // 3 elements
    std::vector<std::string> vec;
    vec.reserve(3);
 
    // constructing & initializing
    // a string with "Hello"
    std::string str("Hello");
 
    // Inserting a copy of string
    // object
    vec.push_back(str);
 
    // Inserting a copy of an
    // temporary string object
    vec.push_back(str + str);
 
    // Again inserting a copy of
    // string object
    vec.push_back(std::move(str));
 
    // Finally, returning the local
    // vector
    return vec;
}


Output

Hello
HelloHello
Hello

Explanation:

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.

Syntax: 

  1. constexpr void push_back(const T& value);                                        (since C++20)
  2. void push_back(T&& value);                                                              (since C++11) (until C++20)
  3. void push_back(const T& value);                                                        (until C++20)
  4. constexpr void push_back(T&& value);                                              (since C++20)
  • vec.push_back(str + str);-
    1. 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.
    2. 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.
    3. 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: 

  1. 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
  2. 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
  3. 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
  4. 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- 

C++14




// C++ program to implement
// the above concept
 
// for std::cout & std::endl
#include <iostream>
 
// for std::move()
#include <utility>
 
// for std::string
#include <string>
 
// for EXIT_SUCCESS macro
#include <cstdlib>
 
// foo() taking a non-const lvalue
// reference argument
void foo(std::string& str);
 
// foo() taking a const lvalue
// reference argument
void foo(const std::string& str);
 
// foo() taking a rvalue
// reference argument
void foo(std::string&& str);
 
// baz() taking a const lvalue
// reference argument
void baz(const std::string& str);
 
// baz() taking a non-const lvalue
// reference argument
void baz(std::string& str);
 
// bar() taking a non-const lvalue
// reference argument
void bar(std::string& str);
 
// constObjectCallFunc() taking a
// rvalue reference argument
void constObjectCallFunc(std::string&& str);
 
// Driver code
int main()
{
    // foo(std::string&& str) will
    // be called
    foo(std::string("Hello"));
 
    std::string goodBye("Good Bye!");
 
    // foo(std::string& str) will be called
    foo(goodBye);
 
    // foo(std::string&& str) will be called
    foo(std::move(goodBye + " using std::move()"));
 
    std::cout << "\n\n\n";
 
    // move semantics fallback
    // baz(const std::string& str) will be called
    baz(std::string("This is temporary string object"));
 
    // baz(const std::string& str) will be called
    baz(std::move(std::string(
        "This is temporary string object using std::move()")));
 
    std::cout << "\n\n\n";
 
    std::string failToCall("This will fail to call");
 
    /*
      Reasons to fail bar() call -
          1. No rvalue reference implementation
           available         // First Preference
          2. No const lvalue reference implementation
           available    // Second Preference
          3. Finally fails to invoke bar() function
      */
    // bar(std::move(failToCall));
    // Error : check the error message for more
    // better understanding
    std::cout << "\n\n\n";
 
    const std::string constObj(
        "Calling a std::move() on a const object usually has no effect.");
    // constObjectCallFunc(std::move(constObj));
    // Error : because of const qualifier
    // It doesn't make any sense to steal or
    // move the resources of a const object
   
    return EXIT_SUCCESS;
}
 
void foo(const std::string& str)
{
    // do something
    std::cout << "foo(const std::string& str) : "
              << "\n\t" << str << std::endl;
}
 
void foo(std::string& str)
{
    // do something
    std::cout << "foo(std::string& str) : "
              << "\n\t" << str << std::endl;
}
 
void foo(std::string&& str)
{
    // do something
    std::cout << "foo(std::string&& str) : "
              << "\n\t" << str << std::endl;
}
 
void baz(const std::string& str)
{
    // do something
    std::cout << "baz(const std::string& str) : "
              << "\n\t" << str << std::endl;
}
 
void baz(std::string& str)
{
    // do something
    std::cout << "baz(std::string& str) : "
              << "\n\t" << str << std::endl;
}
 
void bar(std::string& str)
{
    // do something
    std::cout << "bar(std::string&& str) : "
              << "\n\t" << str << std::endl;
}
 
void constObjectCallFunc(std::string&& str)
{
    // do something
    std::cout << "constObjectCallFunc(std::string&& str) : "
              << "\n\t" << str << std::endl;
}


Output

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()

Summary: 

  • 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.

 



Like Article
Suggest improvement
Share your thoughts in the comments

Similar Reads