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?
Step Through the Code
Walk through the code step by step. Watch how variables change and see the program output at each line.
Variables
Output
Stack / Heap
Output:
Error:
Lesson Progress
- Fix This Code
- Quick Quiz
- Practice Playground - run once