Move Semantics in C++


[!NOTE] You can reference the following videos:


Beginning with C++11, the C++ language introduced move semantics, a mechanism that enables the efficient transfer of resources from one object to another. Move semantics were added to reduce unnecessary copying of heavy objects such as containers, strings, and objects managing dynamic memory or other resources (e.g., file handles).

We provide a detailed and rigorous explanation of move semantics, starting from basic reference types and progressing to move constructors, move assignment operators, and appropriate usage patterns.


2. Value Categories and Reference Types

Understanding move semantics requires understanding how the C++ type system distinguishes between objects that are persistent and objects that are temporary. These distinctions are expressed using value categories and reference types.

2.1 Lvalues

An lvalue is an expression that refers to a persistent object with a name or a stable address. Examples:

int x = 10;   // x is an lvalue

Lvalues can be bound to lvalue references of the form:

T&

2.2 Rvalues

An rvalue is an expression that represents a temporary value that cannot be taken as a stable location. Examples:

10          // rvalue literal
x + 5       // rvalue expression
std::string("hi")  // temporary

Rvalues can be bound to rvalue references of the form:

T&&

2.3 Why This Distinction Matters

The fundamental insight is that temporaries may be safely β€œmoved from”, because they will soon be destroyed. This allows resource transfer rather than duplication.


3. Move Constructors and Move Assignment

C++ classes that manage resources should be designed to support efficient movement. This requires defining the following two special member functions:

Both functions typically take an argument of type T&&.

3.1 Example Class

Consider a simplified string-like class:

class MyString {
private:
    char* data;

public:
    // Constructor
    MyString(const char* s) {
        size_t n = std::strlen(s) + 1;
        data = new char[n];
        std::memcpy(data, s, n);
    }

    // Destructor
    ~MyString() {
        delete[] data;
    }

    // Copy constructor (deep copy)
    MyString(const MyString& other) {
        size_t n = std::strlen(other.data) + 1;
        data = new char[n];
        std::memcpy(data, other.data, n);
    }

3.2 Move Constructor

A move constructor steals the resource pointer and leaves the source object in a valid but empty state:

    MyString(MyString&& other) noexcept
        : data(other.data)
    {
        other.data = nullptr;
    }

3.3 Move Assignment Operator

Move assignment must release the current resources, then take ownership of the source resources:

    MyString& operator=(MyString&& other) noexcept {
        if (this != &other) {
            delete[] data;
            data = other.data;
            other.data = nullptr;
        }
        return *this;
    }
};

3.4 Post-conditions of Move


4. std::move and Enabling Moves

4.1 Purpose of std::move

std::move is a standard utility function that converts its argument into an rvalue reference.

It does not move anything by itself.

It is equivalent to:

static_cast<T&&>(obj)

4.2 Why std::move Is Necessary

Named objects are always lvalues, even if they represent temporary data. Therefore, to call a move constructor or move assignment operator on a named object, you must explicitly convert it into an rvalue reference.

Example:

MyString a("Hello");
MyString b = std::move(a);  // Invokes move constructor

Without std::move, the copy constructor would be called instead.


5. When Moves Occur Automatically

Moves may happen implicitly:

5.1 Returning Local Objects

MyString f() {
    MyString temp("abc");
    return temp;   // eligible for move (or copy elision)
}

Compilers may:

5.2 Insertion into Containers

std::vector<MyString> v;
v.push_back(MyString("Hello"));   // moves temporary

Temporary objects passed by value are perfect candidates for moves.


6. When to Implement Move Semantics

Move semantics should be provided for any class that:

  1. Manages dynamic memory
  2. Owns file handles
  3. Owns sockets or network resources
  4. Uses large buffers or other heavy structures

If your class does not manage any resources, move semantics may provide little or no benefit.


7. Common Pitfalls

7.1 Using a Moved-From Object Incorrectly

After an object has been moved from, it is only safe to:

Using its data members is undefined behavior unless you explicitly define a safe moved-from state.

7.2 Forgetting noexcept

Move constructors that are not marked noexcept may cause standard library containers to fall back to copying instead of moving.

Best practice:

MyClass(MyClass&& other) noexcept { ... }

8. Summary


References

move from scratch