Back to C++
2026-03-295 min read

C++ Move Semantics

Learn C++ Move Semantics step by step with clear examples and exercises.

Why This Matters

Welcome to this full guide on C++ Move Semantics! This lesson is designed to help you understand and master move semantics, a crucial concept in modern C++ programming that can significantly improve the efficiency of your code. We'll cover the core concept, worked examples, common mistakes, practice questions, and frequently asked questions. Let's dive in!

Why This Matters

Move semantics is a powerful feature introduced in C++11 to optimize the performance of copy operations. It allows for efficient resource management by minimizing unnecessary copying, making your code faster and more memory-efficient. Understanding move semantants is essential for writing efficient modern C++ code, especially when dealing with large objects or containers.

Prerequisites

Before diving into move semantics, it's important to have a solid understanding of the following topics:

  1. Basic C++ syntax and concepts
  2. Classes and Objects
  3. Constructors and Destructors
  4. Copy constructors and assignment operators
  5. Reference variables
  6. Rvalue and Lvalue

Core Concept

Move semantics is a technique used to efficiently transfer resources from one object to another during the copying process. Instead of creating a new copy, move semantics moves the resources from the source object to the destination object, leaving the source in a valid but empty state. This can significantly reduce the overhead associated with copy operations, especially when dealing with large objects or containers.

Move Constructors and Assignment Operators

Move constructors (explicit C(C&& src)) and move assignment operators (C& operator=(C&& rhs)) are special functions that facilitate move semantics. They take their arguments by reference to temporary objects (Rvalue references), allowing them to directly transfer the resources from the source object to the destination.

class C {
public:
// Move constructor
C(C&& src) : data_(src.data_) {
src.data_ = nullptr; // Reset source's data
}

// Move assignment operator
C& operator=(C&& rhs) {
if (this != &rhs) {
delete[] data_;
data_ = rhs.data_;
rhs.data_ = nullptr;
}
return *this;
}

private:
int* data_;
};

In the example above, we have defined a simple class C with a move constructor and a move assignment operator. The move constructor takes an Rvalue reference as its argument and transfers the ownership of the data_ pointer from the source object to the destination object by setting the destination's data_ pointer to the source's data_ pointer, then setting the source's data_ pointer to nullptr. The move assignment operator follows a similar approach, deleting the destination's current data and transferring the ownership from the source.

Standard Library Support for Move Semantics

The C++ standard library provides several classes that support move semantics, such as std::vector, std::string, and std::unique_ptr. These classes have move constructors and move assignment operators defined by default, allowing them to take advantage of move semantics during the copying process.

Worked Example

Let's consider an example where we create a large vector of integers and then perform several operations involving copies.

#include <vector>
#include <iostream>

class LargeVector {
public:
LargeVector(size_t size) : data_(new int[size]) {
std::fill(data_, data_ + size, 0);
}

~LargeVector() { delete[] data_; }

private:
int* data_;
};

void copy_large_vector(const LargeVector& src) {
LargeVector dest(src.size());
std::copy(src.begin(), src.end(), dest.begin());
}

void move_large_vector(LargeVector&& src) {
LargeVector dest(src.size());
std::move(src.begin(), src.end(), dest.begin());
}

int main() {
const int vector_size = 1000000;
LargeVector large_vector(vector_size);

// Measure the time for copying using copy_large_vector
auto start = std::chrono::high_resolution_clock::now();
copy_large_vector(large_vector);
auto end = std::chrono::high_resolution_clock::now();
std::cout << "Copying took: " << std::chrono::duration<double>(end - start).count() << " seconds\n";

// Measure the time for moving using move_large_vector
start = std::chrono::high_resolution_clock::now();
move_large_vector(std::move(large_vector));
end = std::chrono::high_resolution_clock::now();
std::cout << "Moving took: " << std::chrono::duration<double>(end - start).count() << " seconds\n";
}

In this example, we have a LargeVector class that represents a large vector of integers. We define two functions, copy_large_vector and move_large_vector, to perform copying and moving operations on the LargeVector. In the main function, we create a large LargeVector, measure the time for copying using copy_large_vector, and then measure the time for moving using move_large_vector.

When you run this code, you'll notice that moving takes significantly less time than copying, demonstrating the efficiency of move semantics.

Common Mistakes

  1. Not defining move constructors or assignment operators: If you don't define move constructors and assignment operators for your classes, the compiler will generate default ones that perform copy operations instead of move operations.
  1. Incorrect use of Rvalue references: It's essential to understand the difference between Rvalue references (C&&) and Lvalue references (const C& or C&). Using Rvalue references incorrectly can lead to compile errors or unexpected behavior.
  1. Not understanding move semantics vs. copy elision: Move semantics and copy elision are often confused. Copy elision is an optimization performed by the compiler that can sometimes eliminate the need for a temporary object during copy construction or assignment, even when no move constructor or move assignment operator is present.

Practice Questions

  1. Write move constructors and move assignment operators for a custom class MyClass with a data member std::string data.
  2. Explain the difference between Rvalue references and Lvalue references, and provide an example of each in C++.
  3. Given the following code snippet:
void foo(C&& c) {
// ...
}

C obj;
foo(obj);

What happens to obj after the call to foo(obj)?

FAQ

  1. Why are move constructors and assignment operators important for performance?

Move constructors and assignment operators allow for efficient resource management by minimizing unnecessary copying, making your code faster and more memory-efficient, especially when dealing with large objects or containers.

  1. Can I write a move constructor without a move assignment operator?

Yes, it's possible to define only a move constructor without a move assignment operator. However, if you have a user-defined copy constructor, the compiler will generate a move assignment operator for you. If you don't want this default move assignment operator, you should explicitly define both the move constructor and the move assignment operator.

  1. What is the difference between std::move and static_cast?

std::move is a function template that returns an Rvalue reference to its argument, while static_cast creates an Rvalue reference from its argument. Both can be used for obtaining Rvalue references, but std::move also has the benefit of performing no conversion if the argument is already an Rvalue.

C++ Move Semantics | C++ | XQA Learn