Move Semantics

Back

Loading concept...

🚀 Move Semantics: The Art of Smart Moving in C++

The Moving Day Story

Imagine you’re moving to a new house. You have two choices:

  1. Copy everything — Buy identical furniture for the new house, leave the old furniture behind (wasteful!)
  2. Move everything — Take the actual furniture to the new house, leaving the old house empty (smart!)

Move semantics in C++ is like choosing option 2. Instead of making expensive copies, we transfer ownership of resources. The old object becomes empty, and the new object gets all the goodies!


🎯 What You’ll Learn

graph TD A["Move Semantics"] --> B["Rvalue References"] A --> C["Move Constructor"] A --> D["Move Assignment"] A --> E["std::move"] A --> F["Perfect Forwarding"] A --> G["std::forward"] A --> H["Rule of 0/3/5"]

📦 Part 1: Rvalue References

What’s an Rvalue?

Think of values like people at a party:

  • Lvalues = People with name tags (you can find them again)
  • Rvalues = Mystery guests (here for one moment, then gone!)
int x = 10;      // x is an lvalue (has a name)
int y = x + 5;   // (x + 5) is an rvalue (temporary)

The && Symbol

We use && to catch these temporary guests:

void greet(int& x) {
    // Only accepts lvalues
}

void greet(int&& x) {
    // Only accepts rvalues (temporaries)
}

Simple Example:

int a = 5;
greet(a);        // Calls greet(int&)
greet(10);       // Calls greet(int&&)
greet(a + 3);    // Calls greet(int&&)

💡 Think of it: & catches things with names. && catches things about to disappear!


🏃 Part 2: Move Semantics

The Big Idea

When something is temporary (about to be destroyed), why copy it? Just steal its resources instead!

graph LR A["Old Object"] -->|Move| B["New Object"] A -->|Becomes| C["Empty Shell"]

Before Move Semantics (The Old Way)

std::vector<int> createBigVector() {
    std::vector<int> v(1000000);
    return v;  // COPY all million elements 😰
}

With Move Semantics (The Smart Way)

std::vector<int> createBigVector() {
    std::vector<int> v(1000000);
    return v;  // MOVE the pointer, not the data! 😊
}

🏗️ Part 3: Move Constructor

What Is It?

A special constructor that takes ownership from a temporary object.

The Recipe

class Box {
    int* data;
    int size;
public:
    // Move Constructor
    Box(Box&& other) noexcept
        : data(other.data)
        , size(other.size)
    {
        other.data = nullptr;  // Leave old box empty
        other.size = 0;
    }
};

Step by Step

  1. Steal the pointer from the old object
  2. Zero out the old object (so it doesn’t delete our stuff!)
graph TD A["other.data → Memory"] -->|Step 1: Steal| B["this.data → Memory"] A -->|Step 2: Nullify| C["other.data → nullptr"]

Real Example:

Box createBox() {
    Box temp;
    temp.fill(100);
    return temp;  // Move constructor called!
}

Box myBox = createBox();  // No copying!

📝 Part 4: Move Assignment Operator

What Is It?

Like move constructor, but for already existing objects.

The Recipe

class Box {
public:
    Box& operator=(Box&& other) noexcept {
        if (this != &other) {
            // 1. Clean up our old stuff
            delete[] data;

            // 2. Steal their stuff
            data = other.data;
            size = other.size;

            // 3. Leave them empty
            other.data = nullptr;
            other.size = 0;
        }
        return *this;
    }
};

The Three Steps

  1. Delete your own resources first
  2. Steal from the source
  3. Nullify the source

Example:

Box box1, box2;
box1 = createBox();  // Move assignment!
box2 = std::move(box1);  // Also move assignment!

🎭 Part 5: std::move

The Magic Wand

std::move doesn’t actually move anything! It just says:

“Hey compiler, treat this lvalue as if it were temporary!”

#include <utility>

int main() {
    std::string a = "Hello";
    std::string b = std::move(a);
    // Now: b = "Hello", a = "" (empty!)
}

When to Use It

// Use std::move when you're DONE with something
void process(std::vector<int> data) {
    // ...
}

std::vector<int> myData = {1, 2, 3};
process(std::move(myData));  // Transfer ownership
// myData is now empty - don't use it!

⚠️ Warning: After std::move, the original object is in a “valid but unspecified” state. Don’t use it!


🎯 Part 6: Perfect Forwarding

The Problem

Imagine you’re a mail carrier. Someone gives you a package:

  • If it’s fragile (lvalue), handle carefully
  • If it’s express (rvalue), deliver fast

How do you remember which is which?

Template Reference Collapsing

template<typename T>
void wrapper(T&& arg) {
    // T&& is a "forwarding reference"
    // It can bind to BOTH lvalues and rvalues!
}

The magic rules:

T T&& becomes
int int&&
int& int&
int&& int&&

The Goal

Pass arguments exactly as received — preserving lvalue/rvalue nature.

template<typename T>
void relay(T&& arg) {
    actualFunction(std::forward<T>(arg));
}

🔄 Part 7: std::forward

The Perfect Solution

std::forward remembers what kind of value you received and passes it along correctly.

#include <utility>

template<typename T>
void wrapper(T&& arg) {
    // Forward as lvalue or rvalue
    // depending on what was passed!
    process(std::forward<T>(arg));
}

How It Works

graph TD A["Lvalue passed"] -->|std::forward| B["Forwarded as Lvalue"] C["Rvalue passed"] -->|std::forward| D["Forwarded as Rvalue"]

Example:

void useValue(int& x) {
    std::cout << "Got lvalue\n";
}
void useValue(int&& x) {
    std::cout << "Got rvalue\n";
}

template<typename T>
void forward_test(T&& x) {
    useValue(std::forward<T>(x));
}

int a = 5;
forward_test(a);    // "Got lvalue"
forward_test(10);   // "Got rvalue"

📏 Part 8: Rule of Zero, Three, and Five

Rule of Zero

If you don’t manage resources directly, don’t define any special functions.

// Perfect! Use smart pointers and containers
class Person {
    std::string name;
    std::vector<int> scores;
    // No destructor, copy, or move needed!
};

Rule of Three (Pre-C++11)

If you define one of these, define all three:

  1. Destructor
  2. Copy Constructor
  3. Copy Assignment

Rule of Five (C++11+)

If you define one of these, define all five:

class Resource {
    int* data;
public:
    // 1. Destructor
    ~Resource() { delete[] data; }

    // 2. Copy Constructor
    Resource(const Resource& other);

    // 3. Copy Assignment
    Resource& operator=(const Resource& other);

    // 4. Move Constructor
    Resource(Resource&& other) noexcept;

    // 5. Move Assignment
    Resource& operator=(Resource&& other) noexcept;
};

Quick Decision Chart

graph TD A{Do you manage raw resources?} A -->|No| B["Rule of Zero&lt;br&gt;Define nothing!"] A -->|Yes| C{Need custom behavior?} C -->|Define all 5| D["Rule of Five"]

🎓 Summary: The Moving Family

Concept Purpose Syntax
Rvalue Reference Catch temporaries T&&
Move Constructor Create by stealing T(T&&)
Move Assignment Assign by stealing T& operator=(T&&)
std::move Cast to rvalue std::move(x)
std::forward Perfect forward std::forward<T>(x)
Rule of 0/3/5 Resource management Define none or all

🌟 Final Thoughts

Move semantics is like learning to pack smart for a trip:

  1. Don’t copy what you can move (save resources)
  2. Leave moved-from objects clean (prevent double-delete)
  3. Use std::move explicitly (be clear about intentions)
  4. Forward perfectly (preserve value categories)
  5. Follow the rules (0, 3, or 5 — pick one!)

Remember: Moving is about efficiency. Copy when you need two things, move when you only need one!

🎯 Pro Tip: Start with Rule of Zero. Only manage resources manually when you absolutely must!

Loading story...

Story - Premium Content

Please sign in to view this story and start learning.

Upgrade to Premium to unlock full access to all stories.

Stay Tuned!

Story is coming soon.

Story Preview

Story - Premium Content

Please sign in to view this concept and start learning.

Upgrade to Premium to unlock full access to all content.