Что такое rvalue в C++?

«Что такое rvalue в C++?» — вопрос из категории C++ Core, который задают на 30% собеседований C/C++ Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В C++ rvalue (правостороннее значение) — это категория выражения, которая обычно обозначает временный, не имеющий имени объект, который нельзя использовать слева от оператора присваивания. Начиная с C++11, введение rvalue-ссылок (&&) стало основой для move-семантики, позволяющей эффективно "перемещать" ресурсы из временных объектов, избегая дорогостоящих глубоких копий.

Ключевые моменты:

  • Примеры rvalue: литералы (42, "hello"), результаты вызовов функций, возвращающих не-ссылку, арифметические выражения (x + 5).
  • Rvalue-ссылки (T&&): Ссылаются только на временные объекты (rvalues). Их основное назначение — реализация конструкторов перемещения и операторов присваивания перемещением.
  • Move-семантика: Позволяет "перехватить" ресурсы (например, динамическую память) из временного объекта, оставив исходный объект в допустимом, но неопределенном состоянии.

Практический пример:

#include <utility> // для std::move
#include <vector>

class MyString {
    char* data;
public:
    // Конструктор перемещения (принимает rvalue-ссылку)
    MyString(MyString&& other) noexcept : data(other.data) {
        other.data = nullptr; // "обнуляем" источник, чтобы его деструктор ничего не удалил
    }

    // Оператор присваивания перемещением
    MyString& operator=(MyString&& other) noexcept {
        if (this != &other) {
            delete[] data;      // освобождаем свои старые ресурсы
            data = other.data;  // забираем ресурсы из `other`
            other.data = nullptr;
        }
        return *this;
    }
    // ... (конструктор копирования, деструктор и т.д.)
};

int main() {
    MyString s1("Hello");
    // std::move превращает lvalue `s1` в rvalue, разрешая вызов конструктора перемещения
    MyString s2 = std::move(s1); // Ресурсы "переехали" из s1 в s2, копирования не было.

    std::vector<int> v1 = {1, 2, 3};
    std::vector<int> v2 = std::move(v1); // Вектор v2 забрал внутренний буфер у v1.
    // Теперь v1 пуст (size() == 0, capacity() == 0).
    return 0;
}