Advanced 14 min

Move Semantics

Master C++11 move semantics to efficiently transfer resources instead of copying them

Move semantics revolutionize C++ performance by enabling efficient transfer of resources from temporary objects instead of expensive copying operations.

A Simple Example

#include <iostream>
#include <cstring>

class String {
private:
    char* data;
    int length;

public:
    String(const char* str = "") {
        length = std::strlen(str);
        data = new char[length + 1];
        std::strcpy(data, str);
        std::cout << "Constructor: " << data << "\n";
    }

    String(const String& other) {
        length = other.length;
        data = new char[length + 1];
        std::strcpy(data, other.data);
        std::cout << "Copy constructor: " << data << "\n";
    }

    String(String&& other) noexcept {
        data = other.data;
        length = other.length;

        other.data = nullptr;
        other.length = 0;

        std::cout << "Move constructor" << "\n";
    }

    String& operator=(String&& other) noexcept {
        if (this == &other) {
            return *this;
        }

        delete[] data;

        data = other.data;
        length = other.length;

        other.data = nullptr;
        other.length = 0;

        std::cout << "Move assignment" << "\n";
        return *this;
    }

    ~String() {
        std::cout << "Destructor" << "\n";
        delete[] data;
    }

    void display() const {
        if (data) {
            std::cout << data << " (length: " << length << ")" << "\n";
        } else {
            std::cout << "(moved-from object)" << "\n";
        }
    }
};

String createString() {
    return String{"Temporary"};
}

int main() {
    String s1{"Hello"};
    String s2 = std::move(s1);

    std::cout << "s1: "; s1.display();
    std::cout << "s2: "; s2.display();

    String s3 = createString();
    s3.display();

    return 0;
}

Breaking It Down

Rvalue References &&

  • What it is: A reference to a temporary object that's about to be destroyed
  • Syntax: Type&& declares an rvalue reference parameter
  • Key insight: Rvalue references let you detect when an object can be stolen from
  • Remember: Lvalues have names and persist, rvalues are temporary and nameless

Move Constructor T(T&& other)

  • What it does: Transfers resources from a temporary to a new object
  • How it works: Steal pointers from source, set source pointers to nullptr
  • Performance: O(1) pointer copy vs O(n) deep copy for large data
  • Remember: Always mark noexcept - moves should never throw exceptions

Move Assignment T& operator=(T&& other)

  • What it does: Transfers resources to an existing object
  • Critical step: Clean up existing resources before stealing new ones
  • Self-assignment check: Always check if (this == &other) first
  • Remember: Leave the source in a valid but unspecified state

std::move() - The Resource Transfer

  • What it does: Casts an lvalue to an rvalue reference, enabling moves
  • Important: std::move() doesn't actually move anything - it enables moving
  • After std::move(): The source object is still valid but in an unspecified state
  • Remember: Don't use an object after moving from it unless you reinitialize it

Why This Matters

  • Copying large objects is expensive - allocate new memory, copy all data, deallocate old memory.
  • But what if you're copying from a temporary that's about to be destroyed anyway?
  • Move semantics let you steal resources from temporaries instead of copying.
  • This is a game-changer for performance, especially with containers like vectors and strings.
  • Understanding move semantics is essential for writing modern, efficient C++ and is a key part of the Rule of Five.

Critical Insight

Move semantics don't move memory - they move pointers! Instead of copying megabytes of data byte-by-byte, you just copy a single 8-byte pointer and null out the source.

Think of it like transferring house ownership: instead of building an identical house and moving all furniture (copy), you just hand over the keys and the original owner moves out (move). The house stays exactly where it is, only the ownership changes.

This is why moved-from objects are valid but unspecified - they're empty shells with no resources.

Best Practices

Always mark move operations noexcept: This enables optimizations in standard containers. Move constructors and assignments should never throw exceptions.

Null out source pointers: After stealing resources, set the source pointers to nullptr to prevent double-deletion crashes.

Check for self-assignment: Always start move assignment with if (this == &other) return *this; to handle edge cases.

Leave moved-from objects valid: The source should be in a state where its destructor can safely run, even if its values are unspecified.

Use std::move() explicitly for lvalues: Don't rely on implicit moves - be explicit when you want to move from a named object.

Common Mistakes

Forgetting to null out pointers: The most common bug. After data = other.data;, you must do other.data = nullptr; to prevent double-deletion.

Using moved-from objects: After std::move(), the source is in an unspecified state. Don't access its values unless you reinitialize it first.

Missing noexcept: Without noexcept, containers won't use your move operations and will fall back to expensive copying.

Not cleaning up in move assignment: Move assignment must delete existing resources before stealing new ones to prevent memory leaks.

Debug Challenge

This move constructor has a critical bug that will cause a crash. Click the highlighted line to fix it:

1 class Buffer {
2 private:
3 int* data;
4 public:
5 Buffer(Buffer&& other) noexcept {
6 data = other.data;
7 }
8 };

Quick Quiz

  1. What is the primary purpose of move semantics?
Transfer resources efficiently instead of copying
Delete objects automatically
Rename variables
  1. What does std::move() actually do?
Casts an lvalue to an rvalue reference
Moves memory to a new location
Deletes the original object
  1. After moving from an object with std::move(), what state is it in?
Valid but unspecified state
Deleted and unusable
Unchanged
  1. Why should move constructors be marked noexcept?
Enables optimizations in containers like std::vector
Prevents memory leaks
Makes code run faster

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.

Lesson Progress

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