Intermediate · Templates, smart pointers and move semantics · topic 29 of 42 · 35 min
Move semantics: steal instead of copy
Explanation
The problem: expensive copies
A std::vector<std::string> with a lakh names holds a big heap buffer. Copying it means allocating a new buffer and copying every string. But often the source is about to disappear anyway: a temporary result from a function, or a local variable you will never use again. Copying in those cases is pure waste. Moving instead just takes over the source's buffer pointer and leaves the source empty: a few pointer assignments, no matter how big the vector is.
lvalues and rvalues
An lvalue is an expression that names an object with an identity you can refer to again, like a variable name. An rvalue is a temporary value that is about to disappear, like the result of a + b or a function returning by value. C++ automatically moves from rvalues because nobody can use them afterwards. For lvalues it copies, because you might still use them, unless you say otherwise with std::move.
std::string makeName() { return "Asha Verma"; }
std::string a = "hello";
std::string b = a; // a is an lvalue: COPY
std::string c = makeName(); // temporary: MOVE (or no copy at all, see below)
std::string d = std::move(a); // std::move: MOVE; a is now valid but unspecified (usually empty)Rvalue references (T&&)
An rvalue reference, written T&&, is a reference that binds only to rvalues. Classes use it to write a move constructor and a move assignment operator: overloads that are chosen when the source is an rvalue, and that steal its resources.
class Buffer {
public:
explicit Buffer(std::size_t n) : size_(n), data_(new int[n]{}) {}
~Buffer() { delete[] data_; }
Buffer(Buffer&& other) noexcept // move constructor
: size_(other.size_), data_(other.data_) {
other.size_ = 0;
other.data_ = nullptr; // leave the source safe to destroy
}
Buffer& operator=(Buffer&& other) noexcept { // move assignment
if (this != &other) {
delete[] data_;
data_ = other.data_;
size_ = other.size_;
other.data_ = nullptr;
other.size_ = 0;
}
return *this;
}
Buffer(const Buffer&) = delete; // copying not supported in this example
Buffer& operator=(const Buffer&) = delete;
private:
std::size_t size_;
int* data_;
};Why noexcept matters
When std::vector grows, it must transfer its elements to the new buffer. If the element's move constructor is marked noexcept, vector moves them (fast). If not, vector copies them instead, to keep its strong exception guarantee. So always mark move operations noexcept when they cannot throw (they usually cannot).
Return values: no std::move needed
std::vector<int> loadMarks() {
std::vector<int> result(100000, 0);
return result; // do NOT write return std::move(result);
}Since C++17, when you return a temporary (prvalue) like return std::string("x"); or initialise from one, the copy or move is guaranteed not to happen at all: the object is built directly in its final place. This is guaranteed copy elision.
Using moves in everyday code
std::vector<std::string> names;
std::string line = readLine();
names.push_back(std::move(line)); // line is not needed any more: move it in
class Student {
public:
explicit Student(std::string name) : name_(std::move(name)) {} // "sink" parameter
private:
std::string name_;
};Rule of zero, again
If your class only holds members like std::string, std::vector and std::unique_ptr, the compiler generates correct move operations for you. You write move constructors yourself only in low-level resource classes. Note that declaring a destructor or copy operation yourself stops the compiler from generating moves, and then the class silently copies instead of moving.
Need help?
Stuck on a word or an idea? Search this course, or every course, and jump straight to the explanation or a worked question.
Try:
Watch the lesson
Move semantics: steal instead of copy · 1/7
Move semantics let C plus plus steal the insides of an object instead of copying them.
Prefer a video by a teacher? Find free videos on “c++ move semantics rvalue reference std::move explained” on YouTube ↗
In real life: Building a big report without copies
A reporting service builds a CSV of lakhs of rows as a std::string and returns it from a function, then puts it into an outbox vector to be emailed. Thanks to move semantics, the huge string is never copied: the return is elided and the push_back takes over its buffer.
#include <iostream>
#include <string>
#include <vector>
std::string buildCsv(int rows) {
std::string csv = "order_id,amount\n";
csv.reserve(static_cast<std::size_t>(rows) * 16);
for (int i = 1; i <= rows; ++i) csv += std::to_string(i) + ",499\n";
return csv; // moved or elided, never copied
}
int main() {
std::vector<std::string> outbox;
std::string report = buildCsv(100000);
outbox.push_back(std::move(report)); // steal the buffer
std::cout << outbox[0].size() << ' ' << report.size() << '\n'; // 988911 0
}Practice (0/4 done)
What does std::move(x) actually do?
std::string a = "data"; std::string b = std::move(a); What is guaranteed about a afterwards?
const std::vector<int> v(1000000); auto w = std::move(v); What happens?
Write a class Playlist holding std::vector<std::string> songs. Give it a constructor that takes std::vector<std::string> by value and moves it into the member, and a function add(std::string song) that moves the song in. In main, show with size() that the source vector is empty after you std::move it into a Playlist.
Quick quiz
Read more (free)
Related help
Before this, learn
This topic builds on these. If something here feels new, open them first.
Related topics
- Templates: code for any typeTemplates, smart pointers and move semantics · same module
- Exceptions: throw, try, catch and noexceptExceptions, concurrency and building projects with CMake
- References: another name for a variableReferences, pointers, arrays and strings
- Arrays, std::array and a first look at std::vectorReferences, pointers, arrays and strings
- std::vector in depthThe Standard Template Library (STL)
- Constructors and destructorsClasses, constructors and RAII
Questions people ask about this
Open a question to see the answer and why. The first ones are this topic's own quiz, so try the quiz above before you peek.
Which is an rvalue?
Answer: The result of a + b
a + b produces a temporary value with no name.
Why should move constructors be noexcept?
Answer: So std::vector moves elements when it grows instead of copying them
vector only uses a move if it cannot throw, to keep its exception guarantee.
What is the best way to return a large local vector from a function?
Answer: return v;
Plain return allows copy elision and is automatically treated as a move otherwise.
A class declares only a destructor (~T() {}) and holds a std::vector. What happens to its moves?
Answer: The implicit move operations are not declared, so moves silently become copies
A user-declared destructor suppresses the implicit move constructor and move assignment.
Which pair makes a deep copy automatically when you copy the object?Related
Answer: A std::vector member
std::vector copies all its elements into new memory.
A noexcept function throws. What happens?Related
Answer: std::terminate is called
Breaking a noexcept promise ends the program.
How do you pass ownership of a unique_ptr into a function sink(std::unique_ptr<T> p)?Related
What is wrong with: const std::string& name() { std::string s = "Asha"; return s; }Related
Answer: It returns a reference to a local that is destroyed: a dangling reference
s dies when the function returns. Return std::string by value.
Pass the quiz (60% or more) or mark this topic as done when you are ready.
Your progress is saved on this device only.
Next steps
- This is the last topic of “Templates, smart pointers and move semantics”. The module exam comes next.
- Module 7 exam: Templates, smart pointers and move semantics · 12 questions · 25 min, without help. 4 topics of this module still to finish first.
- Then the next module, Modern C++: lambdas, C++17 and C++20 essentials. 42 topics left in the course before the final exam.
← Previous: Smart pointers 2: std::shared_ptr and std::weak_ptr