Home Item 3 (1/2) - move semantics (English)
Post
Cancel

Item 3 (1/2) - move semantics (English)

Lvalues & Rvalues

Before discussing std::move, let’s first understand lvalues and rvalues; otherwise, we won’t know what std::move is doing. In C++, lvalues (left values) and rvalues (right values) are two fundamental value type concepts that significantly influence the syntax and semantics of variables, expressions, and operations.

On a nutshell: lvalue: On the left side of the =. rvalue: On the right side of the =.

Lvalues

Lvalues (locator values) represent a memory location. Lvalues can appear on the left side of an assignment operator, meaning they can be assigned new values. Lvalues are typically named variables or expressions that can take addresses.

Characteristics:

  1. Can take addresses (using the & operator).
  2. Can be assigned new values.
  3. Have a persistent storage location in memory.

Example

1
2
3
4
5
int x = 10;    // x is an lvalue. It can take an address &x and can be assigned a new value x = 20.
int* ptr = &x; // ptr is an lvalue. It can take an address &ptr and can be assigned ptr = &y;

x = 5;         // x is an lvalue and can be assigned a new value.

Rvalues

Rvalues (right values) are values that do not represent a specific memory location. Rvalues are usually temporary objects, constants, or the results of expressions. These values cannot take addresses and cannot be assigned new values.

Characteristics:

  1. Cannot take addresses.
  2. Usually temporary objects, destroyed after the expression is evaluated.
  3. Cannot be assigned new values.

Example:

1
2
3
4
5
int y = 10;        // 10 is an rvalue, it is a constant.
int z = x + 5;     // (x + 5) is an rvalue, it is the result of an expression.
int* p = &x;       // p is an lvalue, but &x is an rvalue (address constant).


Lvalue Reference

Nothing special here, if you’ve written C++ before, you’ve likely used it. An lvalue reference binds to an lvalue and can be declared using the & symbol.

1
2
3
int x = 10;
int& ref = x;  // ref is an lvalue reference, bound to lvalue x.

Rvalue Reference

This is more interesting, introduced in C++11. An rvalue reference binds to an rvalue and can be declared using the && symbol. Rvalue references introduce move semantics and perfect forwarding with std::move.

1
2
3
4
5
int&& rref = 20;    // rref is an rvalue reference, bound to rvalue 20.

int x = 10;
int&& rref2 = x + 5; // rref2 is an rvalue reference, bound to the result of the expression (x + 5).

Direct Comparison Example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <iostream>
#include <utility> // std::move

void printValue(const int& lref) {
    std::cout << "Lvalue reference: " << lref << std::endl;
}

void printValue(const int&& rref) {
    std::cout << "Rvalue reference: " << rref << std::endl;
}

int main() {
    int x = 10;            // x is an lvalue
    printValue(x);         // Pass lvalue reference

    printValue(20);        // Pass rvalue reference

    printValue(std::move(x)); // Convert lvalue x to rvalue reference

    auto temp = std::move(x);

    printValue(temp); // temp is still an lvalue

    return 0;
}

Result:

Desktop View

In this example, the printValue function has two overloads, one accepting an lvalue reference and the other an rvalue reference. Depending on whether the argument is an lvalue or rvalue, the corresponding function is called. std::move temporarily converts an lvalue to an rvalue, so the above temp is still an lvalue.

You might not see the use case for std::move yet. Don’t worry, the next article will explain it in detail.

☝ツ☝

This post is licensed under CC BY 4.0 by the author.

👈 ツ 👍