Fundamentals 21 min read

Mastering C++ Move Semantics: Rvalue References and std::move Explained

This article explains C++ move semantics, covering the concepts of lvalues and rvalues, the syntax and rules of rvalue references, how std::move converts lvalues to rvalues, and demonstrates practical applications such as custom class move constructors, STL container optimizations, and return value optimization.

Deepin Linux
Deepin Linux
Deepin Linux
Mastering C++ Move Semantics: Rvalue References and std::move Explained

In the world of C++ programming, object passing and resource management are key areas for performance optimization. Traditional C++ often copies objects when passing them, which can cause significant overhead for large objects.

Lvalue and Rvalue Overview

Before diving into rvalue references and std::move, we must understand the concepts of lvalues (Lvalue) and rvalues (Rvalue) in C++. Lvalues have a stable identity and can be addressed, while rvalues are temporary values without a name.

1.1 Lvalues: Stable Entities

An lvalue is an expression with a persistent storage location that can appear on the left side of an assignment. For example:

int num = 10;
num = 20; // num is an lvalue and can be assigned
int* ptr = # // address of num can be taken

Array elements and function-returned lvalue references are also lvalues:

int arr[5] = {1, 2, 3, 4, 5};
arr[2] = 100; // arr[2] is an lvalue

1.2 Rvalues: Temporary Guests

Rvalues are temporary expressions that cannot be addressed and can only appear on the right side of an assignment. Examples include literal constants and temporary results:

int result = 1 + 2; // 1+2 is an rvalue
int num = 100; // 100 is an rvalue used to initialize num

A function returning a temporary value also yields an rvalue:

int getValue() {
    return 42;
}
int num = getValue(); // getValue() returns an rvalue

1.3 Special Cases and Identification Tricks

When a function returns a local variable, the return value is an rvalue; returning a reference to a global or static variable yields an lvalue. A simple way to distinguish is to try taking the address: if you can, it is an lvalue; otherwise, it is an rvalue.

int a = 10;
int* ptr1 = &a; // a is an lvalue, addressable
int* ptr2 = &(a + 1); // error, a+1 is an rvalue

Rvalue References: References Born for Rvalues

Introduced in C++11, rvalue references provide a dedicated channel for handling temporary objects efficiently.

2.1 Syntax and Binding Rules

An rvalue reference is declared by appending && to a type. It can only bind to rvalues:

int num = 10;
int&& r1 = num; // error, cannot bind to lvalue
int&& r2 = 20 + 30; // OK, binds to rvalue

2.2 Differences Between Rvalue and Lvalue References

Lvalue references bind to lvalues, while rvalue references bind to rvalues. Both can modify the bound object, but a const lvalue reference can bind to an rvalue without allowing modification.

Typical usage:

// Lvalue reference example
void processLvalue(int& value) {
    value *= 2;
}
// Rvalue reference example
void processRvalue(int&& value) {
    value += 10;
}
int main() {
    int num = 5;
    processLvalue(num); // modifies num
    processRvalue(10);   // modifies temporary 10
    return 0;
}

2.3 Lifetime and Use Cases

An rvalue reference can extend the lifetime of a temporary object to match its own lifetime, which is useful when returning temporary objects from functions.

The most common use case is move semantics: transferring resources from a temporary object to another without copying.

std::move: Converting Lvalues to Rvalues

std::move

is a utility that casts an lvalue to an rvalue reference, enabling move semantics.

3.1 How std::move Works

template <typename T>
typename std::remove_reference<T>::type&& move(T&& t) noexcept {
    return static_cast<typename std::remove_reference<T>::type&&>(t);
}

The template accepts any argument (lvalue or rvalue). If an lvalue is passed, T becomes an lvalue reference type; remove_reference strips the reference, and static_cast converts it to an rvalue reference.

3.2 Usage and Caveats

Example:

std::string str = "Hello, World!";
std::vector<std::string> vec;
vec.push_back(std::move(str));

After std::move(str), the string’s resources are moved into the vector, avoiding a copy. The original str remains in a valid but unspecified state and should not be used for its previous value.

3.3 Relationship with Move Semantics

Move semantics rely on move constructors and move assignment operators. std::move provides the rvalue reference that triggers these functions. For a class with a move constructor:

class MyClass {
private:
    int* data;
    int size;
public:
    MyClass(int s) : size(s) { data = new int[s]; }
    // Move constructor
    MyClass(MyClass&& other) noexcept : data(other.data), size(other.size) {
        other.data = nullptr;
        other.size = 0;
    }
    // Move assignment operator
    MyClass& operator=(MyClass&& other) noexcept {
        if (this != &other) {
            delete[] data;
            data = other.data;
            size = other.size;
            other.data = nullptr;
            other.size = 0;
        }
        return *this;
    }
    ~MyClass() { delete[] data; }
};
MyClass obj1(10);
MyClass obj2(std::move(obj1)); // invokes move constructor

Practical Exercises: Applying Rvalue References and std::move

4.1 In Custom Classes

Consider a dynamic array class. The traditional copy constructor and copy assignment perform deep copies, which are costly for large arrays.

class MyDynamicArray {
private:
    int* data;
    int size;
public:
    MyDynamicArray(int s) : size(s) { data = new int[s]; }
    // Copy constructor
    MyDynamicArray(const MyDynamicArray& other) : size(other.size) {
        data = new int[size];
        for (int i = 0; i < size; ++i) data[i] = other.data[i];
        std::cout << "Copy constructor called" << std::endl;
    }
    // Copy assignment
    MyDynamicArray& operator=(const MyDynamicArray& other) {
        if (this != &other) {
            delete[] data;
            size = other.size;
            data = new int[size];
            for (int i = 0; i < size; ++i) data[i] = other.data[i];
        }
        std::cout << "Copy assignment operator called" << std::endl;
        return *this;
    }
    ~MyDynamicArray() { delete[] data; }
};

By adding move constructor and move assignment, we can transfer ownership of the internal buffer instead of copying:

class MyDynamicArray {
    // ... previous members ...
    // Move constructor
    MyDynamicArray(MyDynamicArray&& other) noexcept : data(other.data), size(other.size) {
        other.data = nullptr;
        other.size = 0;
        std::cout << "Move constructor called" << std::endl;
    }
    // Move assignment
    MyDynamicArray& operator=(MyDynamicArray&& other) noexcept {
        if (this != &other) {
            delete[] data;
            data = other.data;
            size = other.size;
            other.data = nullptr;
            other.size = 0;
        }
        std::cout << "Move assignment operator called" << std::endl;
        return *this;
    }
};

4.2 In STL Containers

When inserting into std::vector, using std::move avoids copying large objects:

std::vector<std::string> vec;
std::string str = "Hello";
vec.push_back(std::move(str)); // moves str into the vector

Erasing elements also benefits from move semantics if the stored type implements a move constructor.

4.3 In Function Return Value Optimization

Without move semantics, returning a local object incurs copy construction:

MyDynamicArray createArray() {
    MyDynamicArray arr(5);
    return arr; // copy constructor called
}
MyDynamicArray obj = createArray();

Using std::move (or relying on compiler RVO) triggers the move constructor, transferring resources efficiently:

MyDynamicArray createArray() {
    MyDynamicArray arr(5);
    return std::move(arr); // move constructor called
}
MyDynamicArray obj = createArray();

Thus, rvalue references and std::move enable high‑performance object handling throughout C++ code.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

C++move semanticsrvalue referencesstd::move
Deepin Linux
Written by

Deepin Linux

Research areas: Windows & Linux platforms, C/C++ backend development, embedded systems and Linux kernel, etc.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.