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:
- Destructor
- Copy constructor
- Copy assignment operator
- Move constructor
- 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.
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 →