Move Constructor
Copying a resource-owning object — like the Buffer from the previous page — means allocating new memory and copying every element, even if the original object is about to be destroyed anyway. Moving avoids that waste: instead of duplicating the resource, a move constructor simply transfers ownership of it from one object to another, leaving the source object empty.
The move constructor signature
A move constructor takes its argument as an rvalue reference, written ClassName&&. An rvalue reference binds to temporary objects and to anything explicitly marked as movable with std::move().
A move constructor for Buffer
class Buffer {
public:
int* data;
int size;
Buffer(int n) : data(new int[n]), size(n) {}
// Copy constructor (deep copy — see previous page)
Buffer(const Buffer& other) : data(new int[other.size]), size(other.size) {
for (int i = 0; i < size; ++i) data[i] = other.data[i];
}
// Move constructor: steal 'other's pointer instead of copying the data.
Buffer(Buffer&& other) noexcept : data(other.data), size(other.size) {
other.data = nullptr; // leave 'other' in a valid, empty state
other.size = 0;
}
~Buffer() { delete[] data; } // safe even if data is nullptr
};Notice that the move constructor doesn't allocate anything — it just copies the pointer value itself, then sets the source object's pointer to nullptr. Ownership moved; no new memory was allocated, and no data was copied. The source object (other) is left in a valid but unspecified/empty state: its destructor can still run safely (delete[] nullptr is a no-op), but its data is gone.
std::move: requesting a move explicitly
A named variable is always treated as an lvalue, even if you know you're done with it — so the compiler won't pick the move constructor automatically for it. std::move(x) doesn't actually move anything itself; it just casts x to an rvalue reference, which tells the compiler “it's fine to steal from this.”
std::move in action
#include <utility> // std::move
int main() {
Buffer a(1000);
Buffer b = a; // copy constructor: a is untouched, b gets its own memory
Buffer c = std::move(a); // move constructor: c steals a's memory; a is now empty
// Using 'a' after this point is legal but its buffer is gone (size == 0, data == nullptr).
}The Rule of Five
Modern C++ extends the Rule of Three into the Rule of Five: once a class manages a resource directly, it typically needs all five special member functions defined together — destructor, copy constructor, copy assignment operator, move constructor, and move assignment operator.
The five special member functions
class Buffer {
public:
Buffer(int n); // constructor
~Buffer(); // 1. destructor
Buffer(const Buffer& other); // 2. copy constructor
Buffer& operator=(const Buffer& other); // 3. copy assignment
Buffer(Buffer&& other) noexcept; // 4. move constructor
Buffer& operator=(Buffer&& other) noexcept; // 5. move assignment
};Why this matters: a performance example
Consider a function that builds and returns a large std::vector. Without move semantics, returning it by value would mean copying every element into the caller's variable — potentially millions of copies for a large vector.
Returning a large vector: move makes this cheap
#include <vector>
std::vector<int> makeLargeVector() {
std::vector<int> result(1'000'000, 42);
return result; // moved out, not copied
}
int main() {
// Without move semantics, this would deep-copy 1,000,000 ints.
// With move semantics (and/or copy elision), ownership of the
// vector's internal heap buffer is simply transferred to 'v'.
std::vector<int> v = makeLargeVector();
}What's next
The this pointer is what every member function (including constructors) uses internally to refer to the current object.
The dedicated Move Semantics and Rvalue References pages later in this tutorial go deeper into how moving works throughout the standard library.