Rvalue References
Master rvalue references to enable move semantics and eliminate unnecessary copying
Understand rvalue references and move semantics - the foundation of efficient modern C++ that eliminates expensive copying.
A Simple Example
#include <iostream>
#include <vector>
#include <string>
#include <utility>
class Buffer {
size_t* data;
size_t size;
public:
Buffer(size_t s) : size{s} {
data = new size_t[size];
std::cout << "Buffer created (" << size << " elements)\n";
}
// Copy constructor - expensive
Buffer(const Buffer& other) : size{other.size} {
data = new size_t[size];
std::copy(other.data, other.data + size, data);
std::cout << "Buffer copied (" << size << " elements)\n";
}
// Move constructor - cheap
Buffer(Buffer&& other) noexcept : data{other.data}, size{other.size} {
other.data = nullptr;
other.size = 0;
std::cout << "Buffer moved (" << size << " elements)\n";
}
~Buffer() {
delete[] data;
std::cout << "Buffer destroyed\n";
}
};
Buffer createBuffer() {
return Buffer{1000000}; // Returns temporary - move, not copy
}
int main() {
std::cout << "=== Creating buffer ===\n";
Buffer buf1{createBuffer()}; // Move, not copy!
std::cout << "\n=== Moving buffer ===\n";
Buffer buf2{std::move(buf1)}; // Explicit move
return 0;
}
Breaking It Down
Lvalues vs Rvalues
-
Lvalue: Has a name and address. Can appear on left side of assignment. Example:
int x = 5;(x is an lvalue) -
Rvalue: Temporary value without a name. Cannot take its address. Example:
5,x + y, function return values - Think of it: Lvalues are nouns (things), rvalues are verbs or expressions (actions)
-
Remember: If you can take its address with
&, it is an lvalue
Rvalue References &&
-
Syntax:
T&&binds to rvalues (temporaries) - Purpose: Enables move semantics - transfer ownership instead of copying
- Benefit: Avoid expensive deep copies for large objects
-
Remember: Regular references
T&bind to lvalues,T&&bind to rvalues
Move Constructor
-
Signature:
ClassName(ClassName&& other) noexcept - What it does: Steals resources from the source object and nulls it out
- Performance: O(1) pointer swap vs O(n) deep copy
-
Remember: Always mark move constructors
noexceptfor optimal performance
std::move() - Explicit Move
- What it does: Casts an lvalue to an rvalue reference, enabling move
-
Usage:
Buffer b2{std::move(b1)};- explicitly request move -
Warning: After
std::move(x), x is in a valid but unspecified state - do not use it - Remember: std::move does not actually move anything - it just enables moving
Why This Matters
- Copying large objects like vectors or strings is expensive - sometimes 100x slower than necessary.
- Move semantics let you transfer ownership of resources instead of copying them, dramatically improving performance.
- Understanding lvalues vs rvalues helps you write efficient code that avoids hidden performance costs.
- Modern C++ relies heavily on move semantics - it powers perfect forwarding, smart pointers, and standard containers.
Critical Insight
Move semantics is like transferring ownership of a house. Copying is like building an exact duplicate house next door (expensive!). Moving is like handing over the keys and title - the house stays where it is, you just transfer ownership (cheap!).
When you return a large object from a function, the compiler automatically moves it instead of copying it. This is why modern C++ code can return vectors and strings by value without performance penalties.
Best Practices
Always mark move constructors and move assignment operators noexcept: This enables optimizations in STL containers.
After std::move(), treat the moved-from object as if it no longer exists: Do not access its state except to assign a new value.
Implement move semantics for classes that manage resources: If your class owns heap memory, file handles, or other resources, add move constructor and move assignment.
Return by value for large objects: The compiler automatically uses move semantics, making it efficient.
Common Mistakes
Using an object after std::move(): After moving from an object, it is in an unspecified state. Reassign it before using it.
Forgetting noexcept: Without noexcept, STL containers will not use your move operations and will fall back to copying.
Thinking std::move() actually moves: std::move() only casts to rvalue reference. The actual move happens in the move constructor or move assignment operator.
Not nulling out pointers in move constructor: After transferring ownership, set source pointers to nullptr to prevent double deletion.
Debug Challenge
This code tries to transfer ownership but accidentally copies. Click the highlighted line to fix it:
Quick Quiz
- What happens after you call std::move(x)?
- Why should move constructors be marked noexcept?
- Which is an rvalue?
Practice Playground
Time to try out what you just learned! Play with the example code below, experiment by making changes and running the code to deepen your understanding.
Output:
Error:
Lesson Progress
- Fix This Code
- Quick Quiz
- Practice Playground - run once