Advanced 14 min

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 noexcept for 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:

1 #include <vector>
2
3 int main() {
4 std::vector<int> v1{1, 2, 3, 4, 5};
5 std::vector<int> v2{v1};
6 // v1 should be empty now
7 return 0;
8 }

Quick Quiz

  1. What happens after you call std::move(x)?
x is deleted from memory
x is unchanged
x becomes a nullptr
x is in a valid but unspecified state - do not use it
  1. Why should move constructors be marked noexcept?
noexcept is required by the compiler
noexcept prevents memory leaks
noexcept enables optimizations in containers like std::vector
noexcept makes the code run faster at runtime
  1. Which is an rvalue?
A parameter passed by reference
A static class member
A variable x
The result of x + y

Step Through the Code

Walk through the code step by step. Watch how variables change and see the program output at each line.

Lesson Progress

  • Fix This Code
  • Quick Quiz
  • Practice Playground - run once