Move Semantics & Rvalue References

Efficient resource transfer in modern C++

🚀 What are Move Semantics?

Move semantics allow efficient transfer of resources from temporary objects using rvalue references (&&), avoiding expensive copying operations and improving performance significantly.


std::vector<int> createVector() {
    return std::vector<int>{1, 2, 3, 4, 5}; // Returned by move
}

std::vector<int> vec = createVector(); // Move, not copy!
                                    

Result:

Efficient transfer without copying data

Move Semantics Concepts

📍

Lvalue vs Rvalue

Named objects vs temporary values

int x = 5; // x is lvalue, 5 is rvalue
➡️

Rvalue References

Bind to temporary objects

int&& rref = std::move(x);
🔄

Move Constructor

Transfer resources efficiently

MyClass(MyClass&& other) noexcept

std::move

Cast lvalue to rvalue reference

auto moved = std::move(object);

🔹 Basic Move Example

Move semantics transfer resources from one object to another, avoiding expensive copies. An rvalue reference (&&) binds to temporary objects, enabling the move constructor or assignment operator to "steal" the internal data (like a dynamic array) from the source, leaving it in a valid but empty state. This is critical for performance when dealing with heavy objects like strings or containers. Understanding the difference between lvalues (named, persistent) and rvalues (temporary) is key to using std::move effectively.

#include <iostream>
#include <string>
#include <utility>

void processString(const std::string& s) {
    std::cout << "Lvalue reference: " << s << std::endl;
}

void processString(std::string&& s) {
    std::cout << "Rvalue reference: " << s << std::endl;
}

int main() {
    std::string name = "Alice";
    
    // Lvalue - named object
    processString(name);
    
    // Rvalue - temporary object
    processString("Bob");
    processString(std::string("Charlie"));
    
    // Convert lvalue to rvalue using std::move
    processString(std::move(name));
    
    // name is now in "moved-from" state (valid but unspecified)
    std::cout << "name after move: '" << name << "'" << std::endl;
    
    return 0;
}

Output:

Lvalue reference: Alice
Rvalue reference: Bob
Rvalue reference: Charlie
Rvalue reference: Alice
name after move: ''

🔹 Custom Move Constructor

Implementing move constructors and assignment optimizes resource management in custom classes. The move constructor typically takes an rvalue reference to another instance, transfers its pointers (setting the source's pointers to null), and copies any other trivial members. This leaves the source in a destructible state. Properly implemented move semantics can make operations like returning large objects from functions by value surprisingly efficient, as it often becomes a simple pointer swap instead of a deep copy.

#include <iostream>
#include <cstring>

class MyString {
private:
    char* data;
    size_t length;
    
public:
    // Constructor
    MyString(const char* str) {
        length = strlen(str);
        data = new char[length + 1];
        strcpy(data, str);
        std::cout << "Constructor: " << data << std::endl;
    }
    
    // Copy constructor (expensive)
    MyString(const MyString& other) {
        length = other.length;
        data = new char[length + 1];
        strcpy(data, other.data);
        std::cout << "Copy constructor: " << data << std::endl;
    }
    
    // Move constructor (efficient)
    MyString(MyString&& other) noexcept {
        data = other.data;
        length = other.length;
        other.data = nullptr;
        other.length = 0;
        std::cout << "Move constructor: " << data << std::endl;
    }
    
    // Destructor
    ~MyString() {
        if (data) {
            std::cout << "Destructor: " << data << std::endl;
            delete[] data;
        }
    }
    
    const char* c_str() const { return data ? data : ""; }
};

MyString createString() {
    return MyString("Temporary String"); // Move on return
}

int main() {
    std::cout << "=== Creating temporary ===" << std::endl;
    MyString str1 = createString(); // Move constructor called
    
    std::cout << "\n=== Using std::move ===" << std::endl;
    MyString str2 = std::move(str1); // Move constructor called
    
    std::cout << "\nstr1: " << str1.c_str() << std::endl;
    std::cout << "str2: " << str2.c_str() << std::endl;
    
    return 0;
}

Output:

=== Creating temporary ===
Constructor: Temporary String
Move constructor: Temporary String

=== Using std::move ===
Move constructor: Temporary String

str1:
str2: Temporary String
Destructor: Temporary String

🔹 Perfect Forwarding

Perfect forwarding preserves the value category (lvalue/rvalue) of function arguments. Using a universal reference (template typename T&&) and std::forward<T>, a function template can pass arguments to another function exactly as they were received. This avoids unnecessary copies when forwarding temporaries and maintains the ability to modify lvalues. It's the cornerstone of factory functions, emplacement in containers, and wrapper functions that need to transparently pass arguments through multiple layers.

#include <iostream>
#include <utility>
#include <string>

class Logger {
public:
    void log(const std::string& message) {
        std::cout << "Logging lvalue: " << message << std::endl;
    }
    
    void log(std::string&& message) {
        std::cout << "Logging rvalue: " << message << std::endl;
    }
};

// Perfect forwarding function template
template<typename T>
void forwardToLogger(Logger& logger, T&& message) {
    logger.log(std::forward<T>(message));
}

int main() {
    Logger logger;
    
    std::string msg = "Hello World";
    
    // Forward lvalue
    forwardToLogger(logger, msg);
    
    // Forward rvalue
    forwardToLogger(logger, "Temporary Message");
    forwardToLogger(logger, std::move(msg));
    
    return 0;
}

Output:

Logging lvalue: Hello World
Logging rvalue: Temporary Message
Logging rvalue: Hello World

🧠 Test Your Knowledge

What does std::move do?