Copying is easy to do by accident in C++. Assigning one vector to another or passing a string by value copies every element: for a million numbers, that's a million copies just to move data from one place to another.
Often the original isn't needed any more. If you're about to throw a vector away, it would be far cheaper to hand its memory to the new owner than to duplicate it. That's what moving does: instead of copying the contents, the new object takes over the old object's resources, and the old one is left empty.
In this lesson you'll compare copying with moving, use std::move, write a move constructor, and learn the rules of zero and five.
Copying versus moving
A vector or string is a small object that points to a larger block of memory holding its contents.
- Copying allocates a new block and duplicates every element. Both objects end up with their own, identical data.
- Moving gives the new object the existing block, then sets the old object to empty. Only a few pointers change hands, whatever the size of the data.
vector<int> big(1000000, 7);
vector<int> copy = big; // copies a million ints
vector<int> moved = std::move(big); // takes over big's memoryAfter the move, moved holds the million values and big is empty. The standard library's containers, strings and smart pointers all support moving.
Lvalues, rvalues and std::move
An lvalue is something with a name and a lasting place in memory, like a variable. An rvalue is a temporary value about to disappear, like the result of a + b or a function's return value.
C++ moves automatically from rvalues, because nobody can use a temporary again. For named variables it copies, to keep you safe. When you know you're finished with a variable, std::move(x) tells the compiler "treat x as a temporary; you may move from it". std::move itself doesn't move anything: it just gives permission.
An rvalue reference, written T&&, is a parameter type that binds to temporaries. Functions and constructors that take T&& can safely steal the resources.
Move constructors and moved-from objects
A class supports moving with a move constructor and a move assignment operator:
Buffer(Buffer&& other) noexcept
: data(std::move(other.data)) {}
Buffer& operator=(Buffer&& other) noexcept {
data = std::move(other.data);
return *this;
}Mark them noexcept. A vector that grows only moves its elements if the move can't throw; otherwise it copies them to be safe.
After a move, the source is a moved-from object: valid, safe to destroy or assign a new value to, but its contents are unspecified. Don't read from it until you've given it a new value.
The rule of zero and the rule of five
- Rule of zero: if your class's members are already well-behaved types, such as
string,vectorandunique_ptr, write none of the copy or move functions. The compiler generates correct ones that copy or move each member. - Rule of five: if you must manage a resource by hand, such as raw memory or a file handle, and write a destructor, then also write (or delete) the copy constructor, copy assignment, move constructor and move assignment.
In practice, aim for the rule of zero. Use smart pointers and standard containers for resources, and moves happen correctly without any extra code.
C++ Move Semantics Syntax
Move syntax
T b = std::move(a); // move construct
b = std::move(c); // move assign
T(T&& other) noexcept; // move constructor
T& operator=(T&& other) noexcept;// move assignment
void take(std::string s); // sink parameter: caller can move in
v.push_back(std::move(item));
v.emplace_back(args...); // build in placeC++ Move Semantics Summary
- Copying duplicates every element; moving transfers ownership of the existing memory.
- Temporaries (rvalues) are moved automatically;
std::move(x)allows moving from a named variable. T&&is an rvalue reference, used by move constructors and move assignment.- Moved-from objects are valid but unspecified; assign before reading them again.
- Follow the rule of zero when you can; if you manage a resource yourself, follow the rule of five.
C++ Move Semantics Examples
Each example shows the code, the output it produces and what each part does.
Copy versus move
#include <iostream>
#include <string>
#include <vector>
using namespace std;
struct Buffer {
string name;
vector<int> data;
Buffer(string n, size_t size) : name(n), data(size) {
cout << "create " << name << endl;
}
Buffer(const Buffer& other) : name(other.name), data(other.data) {
cout << "copy " << name << " (" << data.size() << " ints duplicated)" << endl;
}
Buffer(Buffer&& other) noexcept
: name(std::move(other.name)), data(std::move(other.data)) {
cout << "move " << name << " (no ints duplicated)" << endl;
}
};
int main() {
Buffer a("photo", 1000000);
Buffer b = a;
Buffer c = std::move(a);
cout << "a now holds " << a.data.size() << " ints" << endl;
cout << "c holds " << c.data.size() << " ints" << endl;
return 0;
}create photo
copy photo (1000000 ints duplicated)
move photo (no ints duplicated)
a now holds 0 ints
c holds 1000000 intsLine-by-line explanation
- Lines 13 to 15The copy constructor duplicates the whole vector.
- Lines 16 to 19The move constructor takes over other's string and vector instead.
- Line 24Copying from a named variable is the default.
- Line 25std::move gives permission to move, so the move constructor runs.
- Line 26A vector that has been move-constructed from is left empty.
push_back, std::move and emplace_back
#include <iostream>
#include <string>
#include <vector>
using namespace std;
struct Song {
string title;
Song(string t) : title(std::move(t)) { cout << "make " << title << endl; }
Song(const Song& s) : title(s.title) { cout << "copy " << title << endl; }
Song(Song&& s) noexcept : title(std::move(s.title)) { cout << "move " << title << endl; }
};
int main() {
vector<Song> playlist;
playlist.reserve(3);
Song intro("Intro");
playlist.push_back(intro);
playlist.push_back(std::move(intro));
playlist.emplace_back("Outro");
cout << playlist.size() << " songs" << endl;
return 0;
}make Intro
copy Intro
move Intro
make Outro
3 songsLine-by-line explanation
- Line 15Reserving space stops the vector moving its contents while it grows, which would add extra lines.
- Line 17Creating the song prints "make Intro".
- Line 18intro is still needed, so push_back copies it.
- Line 19After std::move, push_back moves it instead.
- Line 20emplace_back builds the Song directly inside the vector from "Outro", with no copy or move at all.
A sink parameter
#include <iostream>
#include <string>
#include <vector>
using namespace std;
class Inbox {
vector<string> messages;
public:
void add(string msg) {
messages.push_back(std::move(msg));
}
vector<string> takeAll() {
vector<string> out = std::move(messages);
messages.clear();
return out;
}
size_t count() const { return messages.size(); }
};
int main() {
Inbox inbox;
string greeting = "Welcome to Codevra";
inbox.add(greeting);
inbox.add("Your order has shipped");
cout << "Still have greeting: " << greeting << endl;
vector<string> all = inbox.takeAll();
cout << "Took " << all.size() << ", inbox now has " << inbox.count() << endl;
return 0;
}Still have greeting: Welcome to Codevra
Took 2, inbox now has 0Line-by-line explanation
- Lines 9 to 11Take the parameter by value, then move it into the vector. Callers can pass a copy or a temporary, and only the minimum work happens.
- Lines 13 to 14Move the whole vector out, then clear the moved-from one so it's in a known state.
- Line 23greeting is a named variable, so add receives a copy and greeting is unchanged.
- Line 24A temporary string is moved all the way into the vector.
Common C++ Move Semantics Mistakes
Using a variable after moving from it
vector<int> a = {1, 2, 3};
vector<int> b = std::move(a);
cout << a[0]; // a is empty now: undefined behaviourvector<int> b = std::move(a);
a = {4, 5, 6}; // give it a new value before using it againstd::move on return
vector<int> build() {
vector<int> v(1000);
return std::move(v); // can block the compiler's optimisation
}vector<int> build() {
vector<int> v(1000);
return v; // moved or built in place automatically
}Moving from a const object
const string name = "Ada";
string other = std::move(name); // silently copies: const can't be moved fromstring name = "Ada";
string other = std::move(name);C++ Move Semantics in Real Projects
Moves in practice
Moves make the standard library fast: sorting strings swaps internal pointers instead of text, and returning large vectors is cheap. unique_ptr can only be moved, which is how C++ expresses passing ownership.
C++ Move Semantics FAQ
Why can't I pass a named variable to a T&& parameter?
An rvalue reference parameter like void store(string&& s) only binds to temporaries, so store(name) fails with "cannot bind rvalue reference". Write store(std::move(name)) to say you're finished with name. Inside store, s has a name, so it counts as an lvalue again: to move from it, you need std::move(s) once more.
What is std::forward used for?
In a template, a parameter written T&& is a forwarding reference: it accepts both lvalues and rvalues. std::forward<T>(x) passes x on exactly as it arrived, moving if the caller gave a temporary and copying if they gave a named variable. Use std::move for ordinary rvalue references, and std::forward only in these templates.
Is moving a short string faster than copying it?
Not much. Standard library implementations use the small string optimisation: short strings, typically up to about 15 characters with g++, are stored inside the string object itself with no separate memory block, so a move has to copy those characters too. Moving pays off for long strings and for containers, where it swaps a pointer instead of copying all the data.
How do I make a class that can be moved but not copied?
Delete the copy operations and default the move ones: NoCopy(const NoCopy&) = delete;, NoCopy& operator=(const NoCopy&) = delete;, then NoCopy(NoCopy&&) = default; and NoCopy& operator=(NoCopy&&) = default;. Copying then fails with "use of deleted function". If the class holds a unique_ptr member, you get this behaviour automatically.
What does std::exchange do in a move constructor?
std::exchange(obj, newValue) from <utility> sets obj to newValue and returns the old value in one step. In a hand-written move constructor, data(std::exchange(other.data, nullptr)) takes the other object's pointer and leaves nullptr behind, so the moved-from object's destructor won't free the memory a second time. It's a neat way to write the "steal and reset" pattern.
C++ Move Semantics Exercises
Answer each one and check it instantly. If you get stuck, open the hint before the answer.
Complete the line so b takes over a's contents instead of copying them.
vector<int> b = std::(a);
What happens to a vector after it's been moved from with the move constructor?
What does T&& mean as a parameter type?
Which adds an object by building it directly inside the vector?
Why mark a move constructor noexcept?
C++ Move Semantics Quiz
5 questions to check what you learned.
What's the main benefit of moving over copying?
- Moving changes the values
- Moving is required for ints
- Moving sorts the data
- Moving transfers existing memory instead of duplicating every element
Does
std::moveitself move anything?- Yes, it copies then deletes
- Yes, but only for strings
- No, it only marks a value as allowed to be moved from
- Only when used with new
What does the rule of zero recommend?
- Use well-behaved members like string, vector and unique_ptr, and write none of the copy or move functions
- Always write five special functions
- Never use classes
- Avoid constructors
What can you safely do with a moved-from object?
- Read its old values
- Nothing at all
- Move from it again to get the data back
- Destroy it or assign it a new value
Why not write
return std::move(localVariable);?- It doesn't compile
- The compiler already moves or constructs the return value in place, and std::move can prevent that
- It copies twice
- Return values can't be moved