← Назад к вопросам

Что такое lvalue?

2.0 Middle🔥 211 комментариев
#Язык C++

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI28 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Что такое lvalue?

lvalue (left value) — это выражение, которое ссылается на объект с постоянным адресом в памяти. Название происходит из того, что такое выражение может стоять слева от оператора присваивания.

Определение и характеристики

lvalue — это выражение, которое:

  1. Имеет адрес в памяти — к нему можно применить оператор &
  2. Сохраняется после выражения — объект существует после вычисления выражения
  3. Может быть использован слева от присваиванияlvalue = выражение
  4. Имеет тип — рассматривается как конкретный тип в системе типов
int x = 5;           // x — lvalue
int& ref = x;        // x — lvalue, может быть привязана ссылка
int* ptr = &x;       // & применён к lvalue
x = 10;              // x слева от присваивания

int y = x + 5;       // (x + 5) — rvalue, результат временный

Примеры lvalue

#include <iostream>
using namespace std;

int main() {
    int a = 10;              // a — lvalue
    int b = 20;              // b — lvalue
    int& ref = a;            // ref — lvalue (ссылка на lvalue)
    
    a = b;                   // a и b — оба lvalue
    a = 30;                  // a — lvalue слева от =
    
    // Адреса существуют
    int* ptr_a = &a;         // &a работает, a — lvalue
    int* ptr_b = &b;         // &b работает, b — lvalue
    
    // Функции, возвращающие lvalue ссылки
    int& get_a() { return a; }
    int& result = get_a();    // result — lvalue
    
    return 0;
}

lvalue vs rvalue

rvalue (right value) — это выражение, которое НЕ имеет стабильного адреса в памяти. Это временные объекты, которые уничтожаются после выражения.

int x = 5;           // 5 — rvalue (литерал)
int y = x + 3;       // (x + 3) — rvalue (временный результат)
int z = foo();       // foo() — rvalue (возвращаемое значение)

// ❌ ОШИБКА: нельзя привязать non-const ссылку к rvalue
int& ref = 5;        // Ошибка компилятора!

// ✅ МОЖНО: const ссылка может привязаться к rvalue
const int& ref = 5;  // OK, const ссылка

Различие в контексте присваивания

int a = 10;          // a — lvalue
int b = 20;          // b — lvalue

// Слева от = должно быть lvalue
a = b;               // ✅ OK
b = a;               // ✅ OK

// Справа может быть lvalue или rvalue
a = b + 10;          // ✅ OK (b+10 — rvalue)
10 = a;              // ❌ ОШИБКА (10 — rvalue, не может быть слева)

Ссылки на lvalue

Основной способ работы с lvalue — это ссылки на lvalue (lvalue references).

int x = 42;

// Ссылка на lvalue
int& ref = x;        // Привязываем к существующему объекту
ref = 100;           // Изменяем через ссылку
cout << x << endl;   // Выводит 100

// Функция принимает ссылку на lvalue
void modify(int& value) {
    value += 10;
}

modify(x);           // Передаём lvalue
cout << x << endl;   // Выводит 110

// ❌ Нельзя привязать неконстантную ссылку к rvalue
int& ref2 = 42;      // ОШИБКА!
int& ref3 = x + 10;  // ОШИБКА!

Категории выражений в C++17

С C++17 система категоризации выражений стала более сложной:

// glvalue (generalized lvalue) — имеет адрес
// ├── lvalue — именованный объект, функция
// └── xvalue — eXpiring value (семантика move)

// prvalue (pure rvalue) — временный объект
int x = 10;

// lvalue
int& ref1 = x;           // x — lvalue

// xvalue (результат std::move)
int&& ref2 = std::move(x);  // std::move(x) — xvalue

// prvalue
int&& ref3 = 42;         // 42 — prvalue
int&& ref4 = x + 10;     // (x + 10) — prvalue

Практические примеры использования lvalue

#include <iostream>
#include <vector>
using namespace std;

class Container {
private:
    vector<int> data;

public:
    // Возвращает lvalue ссылку на элемент
    int& operator[](size_t index) {
        return data[index];
    }
    
    // Копирование через lvalue
    Container(const Container& other) : data(other.data) {}
    Container& operator=(const Container& other) {
        if (this != &other) {
            data = other.data;
        }
        return *this;
    }
};

int main() {
    Container c1;
    c1[0] = 42;           // operator[] возвращает lvalue ссылку
    
    Container c2 = c1;    // Копирующий конструктор (принимает lvalue)
    Container c3;
    c3 = c1;              // Копирующий оператор присваивания
    
    return 0;
}

Почему это важно для бэкендера?

  1. Производительность — понимание lvalue/rvalue помогает избежать ненужных копий
  2. Move семантика — С C++11 можно оптимизировать передачу данных через move
  3. Perfect forwarding — правильно передавать аргументы без копирования
  4. Системное программирование — работа с адресами, указателями, памятью
  5. API дизайн — выбор между ссылками и значениями
// Пример: функция, которая работает с обоими типами
template<typename T>
void process(T&& value) {  // universal reference
    if (is_lvalue_reference<T>::value) {
        cout << "Получили lvalue" << endl;
    } else {
        cout << "Получили rvalue" << endl;
    }
}

int x = 42;
process(x);        // lvalue
process(42);       // rvalue

Вывод: lvalue — это выражение, которое ссылается на объект с постоянным адресом в памяти. Это фундаментальное понятие для системного программирования на C++, позволяющее правильно работать со ссылками, указателями и оптимизировать код через move семантику.