Что такое rvalue в 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;
}

Ответ 18+ 🔞

А, ну вот, опять эти ваши rvalue-ссылки, move-семантика... Ёпта, сидишь такой, читаешь, и волнение ебать — как будто на дворе 2002-й год, и тебе только что объяснили, что копировать большие объекты — это пиздец как дорого.

Смотри, чувак, представь себе: у тебя есть коробка с дорогим хрусталём. Это твой объект в памяти. Раньше, если ты хотел отдать её другому, ты делал полную копию: покупал новую коробку, аккуратно перекладывал каждый бокал, тратил кучу времени и сил. Это копирование. А теперь представь, что этот «другой» — это просто твоё же будущее «я», через секунду. Зачем копировать? Можно просто взять и передать коробку из рук в руки, а в старых руках оставить пустоту. Это и есть перемещение (move semantics).

Так вот, rvalue — это как раз та самая коробка, которую ты готов отдать, потому что она временная. Литерал 42, результат a + b, объект, который вот-вот умрёт — всё это rvalue. И чтобы ловить эти самые «готовые к передаче» объекты, в C++11 ввели rvalue-ссылкиT&&.

Их главная фишка — конструктор перемещения и оператор присваивания перемещением. Они говорят: «О, я вижу, тебе на эту память уже похуй (потому что ты rvalue) — давай-ка я её себе заберу, а тебя обнулю, чтобы ты потом по ошибке не удалил моё добро».

Вот смотри на код, тут всё наглядно:

#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;
}

Видишь магию? std::move(s1) — это не перемещение. Это просто такой хуй в пальто, который говорит компилятору: «Эй, смотри на s1 как на rvalue, пусть с ним делают что хотят». И компилятор такой: «Ага, раз это rvalue, то вместо дорогого копирования вызову-ка я конструктор перемещения, который просто перепривяжет указатель». И всё, ресурс переехал. А s1 остался жить, но в состоянии «манда с ушами» — формально жив, но его внутренности обнулены.

И самое охуенное, что вся стандартная библиотека на это переписана. Вектора, строки, умные указатели — все они умеют перемещаться. Поэтому когда ты пишешь v2 = std::move(v1), то внутри не происходит овердохуища копий каждого элемента. Вместо этого v2 просто говорит: «О, я вижу временный объект, дай-ка я схвачу его внутренний буфер за горло и присвою себе». И v1 остаётся пустым, но валидным.

Короче, суть в чём: rvalue-ссылки и move-семантика — это способ сказать компилятору: «Чувак, я вижу, что этот объект больше не нужен. Давай не будем делать глубокое копирование, как идиоты, а просто перекинем владение его ресурсами. Быстро, дёшево и сердито». И производительность взлетает, особенно когда работаешь с контейнерами. Ёперный театр, как же это удобно!