CLOSE
Updated on 17 Jun, 202518 mins read 17 views

Copying objects is expensive. Whether it’s a large std::vector, a buffer, or a file handle — copying means duplicating memory, and that costs performance.

Move semantics, introduced in C++11, is a powerful solution that allows us to transfer ownership of resources rather than duplicating them. It enables efficient memory and resource management — especially for temporary objects.

What are Move Semantics?

Move semantics allow an object to steal the internal resources (e.g., dynamic memory, file handles) of another object rather than copying them.

It involves:

  • Move constructor

  • Move assignment operator

Both are designed to transfer data from one object to another, leaving the source in a valid but empty state.

Why Do We Need Move Semantics?

❌ The Problem with Copying

Suppose you have a class that holds a dynamically allocated array:

class Buffer {
    int* data;
    size_t size;
public:
    Buffer(size_t s) : size(s) {
        data = new int[size];
    }
    ~Buffer() {
        delete[] data;
    }
    Buffer(const Buffer& other);             // Copy constructor
    Buffer& operator=(const Buffer& other);  // Copy assignment
};

Copying a Buffer involves deep copying the internal array — expensive for large data.

✅ Enter Move Semantics

With move semantics, you can write:

Buffer(Buffer&& other);             // Move constructor
Buffer& operator=(Buffer&& other);  // Move assignment

This allows you to transfer ownership of data from one object to another without copying the contents.

Copy vs Move: Key Differences

FeatureCopyMove
PurposeDuplicate resourceTransfer resource ownership
PerformanceSlow (deep copy)Fast (pointer swap, no copy)
Used whenLvalues (named variables)Rvalues (temporaries, std::move)

A Minimal Example

Step 1: A Class With Move Semantics

class Buffer {
    int* data;
    size_t size;

public:
    Buffer(size_t s) : size(s) {
        data = new int[size];
        std::cout << "Constructed\n";
    }

    ~Buffer() {
        delete[] data;
        std::cout << "Destroyed\n";
    }

    // Copy constructor
    Buffer(const Buffer& other) : size(other.size) {
        data = new int[size];
        std::copy(other.data, other.data + size, data);
        std::cout << "Copied\n";
    }

    // Move constructor
    Buffer(Buffer&& other) noexcept : data(other.data), size(other.size) {
        other.data = nullptr;
        other.size = 0;
        std::cout << "Moved\n";
    }

    // Move assignment
    Buffer& operator=(Buffer&& other) noexcept {
        if (this != &other) {
            delete[] data;  // clean up current resource

            data = other.data;
            size = other.size;

            other.data = nullptr;
            other.size = 0;

            std::cout << "Move Assigned\n";
        }
        return *this;
    }
};

Step 2: Usage Example

Buffer makeBuffer() {
    Buffer temp(1000);
    return temp;  // Move constructor is called here
}

int main() {
    Buffer b1 = makeBuffer();      // Moved from temp
    Buffer b2 = std::move(b1);     // Explicit move
}

When to Use std::move

Use std::move(obj) when you:

  • Want to explicitly transfer ownership

  • Know that obj won’t be used again in its original form

Example:

Buffer a(1000);
Buffer b = std::move(a);  // a is now in an empty/valid state

⚠️ After std::move(a), the original a should not be used (except maybe to destroy or reset).

STL & Move Semantics

The Standard Template Library (STL) uses move semantics heavily:

  • std::vector will move elements instead of copying them when resizing

  • std::unique_ptr is non-copyable, but movable

  • emplace_back() uses move semantics by default

This makes your code:

  • Faster

  • Safer

  • Cleaner

The Role of std::move

std::move is an utility function that facilitates the use of move semantics. It is defined in the <utility> header and is a key component in implementing efficient resource transfers. The primary purpose of std::move is to cast an object tot an rvalue reference, indicating that it can moved from.

Basic Syntax:

The syntax of std::move is simple:

#include <utility>

T&& std::move(T&& arg);

Here, T represents the type of the object, and arg is the object to be cast to an rvalue reference. The result of std::move is an rvalue reference of type T.

How std::move Works

When you apply std::move to an object, you are essentially telling the compiler that you are done with the object, and it can safely “move” the resources it owns. This is particularly useful in scenarios involving dynamic memory allocation or resource management.

Consider the following example:

#include <iostream>
#include <vector>

int main() {
    std::vector<int> source = {1, 2, 3, 4, 5};
    std::vector<int> destination = std::move(source);

    std::cout << "Source size: " << source.size() << std::endl; // Undefined behavior
    std::cout << "Destination size: " << destination.size() << std::endl; // Outputs 5

    return 0;
}

// Output
Source size: 0
Destination size: 5

In this example, std::move is used to transfer the contents of the source vector to the destination vector. After the move, source is left in a valid but unspecified state, and accessing its size is undefined behavior.