Open In App

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

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++ 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-

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. 

Example: Below is the C++ program to implement the above concept using move semantics i.e. since C++11 and later. 




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

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++ 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: 

 


Article Tags :