C++ by Example
C++ by Example

Copy and Move Semantics

2 min read

Copy and Move Semantics

Every time an object is copied or assigned, C++ calls specific functions — the copy constructor and copy assignment operator. C++11 added move semantics: a second set of functions that transfer resources instead of duplicating them, making operations like returning a large vector from a function nearly free.

The Rule of Five

If a class manages a resource (raw memory, file handle, socket), you typically need to define five special member functions. If the default behavior is wrong for one, it is likely wrong for all:

  1. Destructor
  2. Copy constructor
  3. Copy assignment operator
  4. Move constructor
  5. Move assignment operator
#include <iostream>
#include <cstring>

class Buffer {
    char* data;
    size_t size;

public:
    // Constructor
    explicit Buffer(size_t n) : data(new char[n]()), size(n) {
        std::cout << "construct\n";
    }

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

    // Copy constructor — deep copy
    Buffer(const Buffer& other) : data(new char[other.size]), size(other.size) {
        std::memcpy(data, other.data, size);
        std::cout << "copy construct\n";
    }

    // Copy assignment — deep copy
    Buffer& operator=(const Buffer& other) {
        if (this == &other) return *this;   // self-assignment guard
        delete[] data;
        size = other.size;
        data = new char[size];
        std::memcpy(data, other.data, size);
        std::cout << "copy assign\n";
        return *this;
    }

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

    // Move assignment — transfer ownership
    Buffer& operator=(Buffer&& other) noexcept {
        if (this == &other) return *this;
        delete[] data;
        data = other.data;
        size = other.size;
        other.data = nullptr;
        other.size = 0;
        std::cout << "move assign\n";
        return *this;
    }
};

Copy vs. move

A copy duplicates the data. A move steals it — the source is left in a valid but unspecified state (often empty):

Buffer a(1024);
Buffer b = a;             // copy — two independent buffers
Buffer c = std::move(a);  // move — c has a's data, a is now empty

Moving is typically O(1) regardless of the object size. Copying is O(n) in the size of the data.

The Rule of Zero

Most modern C++ classes should follow the Rule of Zero: define none of the five special functions. Use smart pointers and containers to manage resources, and let their destructors and move/copy operations do the work:

class Document {
    std::string title;
    std::vector<std::string> paragraphs;
    // No destructor, copy, or move needed
    // std::string and std::vector handle everything
};

When moves happen

The compiler generates move operations automatically for:

  • Return values from functions (NRVO may eliminate the move entirely)
  • std::move() casts
  • Temporary objects
std::vector<int> make_big_vector() {
    std::vector<int> v(1'000'000, 0);
    return v;   // moved (or elided) — no copy of a million ints
}

noexcept on move operations

Mark move constructors and move assignment operators noexcept. Standard library containers (like std::vector) check whether moves are noexcept — if they are, the container moves elements during reallocation; if not, it copies them (for strong exception safety). A non-noexcept move undermines performance.

C++ by Example
C++ by Example

Learn modern C++ through working code. Each chapter introduces one concept — variables, functions, classes, templates, smart pointers, concurrency — with a clear example, a line-by-line explanation, and notes on how it applies in real programs.

View book →