Open In App

Move Semantics, Move Constructors and Move Assignment Operators

Last Updated : 31 Jul, 2025
Comments
Improve
Suggest changes
Like Article
Like
Report

In C++, there are two types of references:

  • Lvalue Reference (&): Refers to variables or objects with a name and a memory address; can appear on both sides of an assignment.
  • Rvalue Reference (&&): Refers to temporary objects without a persistent name; appears only on the right-hand side of an assignment.

Move Constructor And Semantics:

The move constructor, introduced in C++11, enables efficient resource transfer from one object to another by "stealing" resources instead of copying them, avoiding unnecessary overhead. It uses rvalue references (&&), unlike copy constructors which use lvalue references (&). After the move, the source object is left in a valid but unspecified state. The std::move() function, defined in <utility>, is used to convert lvalues to rvalues to enable moving resources efficiently.

Syntax:

C++
// (since C++11) (until C++14)
template< class T >
typename std::remove_reference<T>::type&& move(T&& t) noexcept;

// (since C++14)
template< class T >
constexpr std::remove_reference_t<T>&& move(T&& t) noexcept;

Example: Below is the C++ program to show what happens without using move semantics i.e. before C++11.

C++14
#include <iostream>
#include <memory>
#include <vector>
using namespace std;

vector<string> createAndInsert() {
    
    // Constructing a vector of
    // strings with an size of
    // 3 elements
    vector<string> vec;
    vec.reserve(3);

    // constructing & initializing
    // a string with "Hello"
    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(str);

    return vec;
}

int main() {
    
    // Constructing an empty vector
    // of strings
    vector<string> vecString;

    // calling createAndInsert() and
    // initializing the local vecString
    // object
    vecString = createAndInsert();

    // Printing content of the vector
    for (const auto& s : vecString) {
        cout << s << '\n';
    }

    return 0;
}

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++
#include <iostream>
#include <memory>
#include <vector>
using namespace std;

vector<string> createAndInsert() {
    
    // constructing a vector of
    // strings with an size of
    // 3 elements
    vector<string> vec;
    vec.reserve(3);

    // constructing & initializing
    // a string with "Hello"
    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(move(str));
    
    return vec;
}

int main() {
    
    // Constructing an empty vector
    // of strings
    vector<string> vecString;

    // calling createAndInsert() and
    // initializing the local vecString
    // object
    vecString = createAndInsert();

    // Printing content of the vector
    for (const auto& s : vecString) {
        cout << s << '\n';
    }

    return 0;
}

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.

The push_back() method takes its parameter by const reference, but since std::vector stores its elements by value, a deep copy of str is created and inserted into the vector. This involves calling the copy constructor of std::string.

Syntax:

C++
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);-
    1. A temporary object str + str is created.
    2. This temporary object is then passed to vec.push_back(). Since std::vector stores elements by value, it will create a copy of this temporary object.
    3. The temporary object is destroyed as it is no longer needed.
  • vec.push_back(std::move(str));-
  • std::move() casts str to an rvalue reference, and push_back() will move the contents of str into the vector, leaving str in a valid but unspecified state.
  • return vec; - This typically involves returning the local vector vec. While a deep copy of the vector would normally be created, modern compilers often use Return Value Optimization (RVO) to eliminate unnecessary copying.

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 has all necessary types of arguments.

C++14
#include <iostream>
#include <memory>
#include <vector>

using namespace std;

// foo() taking a non-const lvalue
// reference argument
void foo(string& str);

// foo() taking a const lvalue
// reference argument
void foo(const string& str);

// foo() taking a rvalue
// reference argument
void foo(string&& str);

// baz() taking a const lvalue
// reference argument
void baz(const string& str);

// baz() taking a non-const lvalue
// reference argument
void baz(string& str);

// bar() taking a non-const lvalue
// reference argument
void bar(string& str);

// constObjectCallFunc() taking a
// rvalue reference argument
void constObjectCallFunc(string&& str);

int main() {
    
	// foo(string&& str) will
	// be called
	foo(string("Hello"));

	string goodBye("Good Bye!");

	// foo(string& str) will be called
	foo(goodBye);

	// foo(string&& str) will be called
	foo(move(goodBye + " using move()"));

	cout << "\n\n\n";

	// move semantics fallback
	// baz(const string& str) will be called
	baz(string("This is temporary string object"));

	// baz(const string& str) will be called
	baz(move(string(
		"This is temporary string object using move()")));

	cout << "\n\n\n";

	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(move(failToCall));
	// Error : check the error message for more
	// better understanding
	cout << "\n\n\n";

	const string constObj(
		"Calling a move() on a const object usually has no effect.");
	// constObjectCallFunc(move(constObj));
	// Error : because of const qualifier
	// It doesn't make any sense to steal or
	// move the resources of a const object

	return 0;
}

void foo(const string& str) {
    
	// do something
	cout << "foo(const string& str) : "
			<< "\n\t" << str << endl;
}

void foo(string& str) {
    
	// do something
	cout << "foo(string& str) : "
			<< "\n\t" << str << endl;
}

void foo(string&& str) {
    
	// do something
	cout << "foo(string&& str) : "
			<< "\n\t" << str << endl;
}

void baz(const string& str) {
    
	// do something
	cout << "baz(const string& str) : "
			<< "\n\t" << str << endl;
}

void baz(string& str) {
    
	// do something
	cout << "baz(string& str) : "
			<< "\n\t" << str << endl;
}

void bar(string& str) {
    
	// do something
	cout << "bar(string&& str) : "
			<< "\n\t" << str << endl;
}

void constObjectCallFunc(string&& str) {
    
	// do something
	cout << "constObjectCallFunc(string&& str) : "
			<< "\n\t" << str << endl;
}


Output

foo(string&& str) : 
Hello
foo(string& str) :
Good Bye!
foo(string&& str) :
Good Bye! using move()



baz(const string& str) :
This is temporary string object
baz(const string& str) :
This is temporary string object using 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.
     

Similar Reads