MyTetra Share
Делитесь знаниями!
О, смотри-ка какое хорошее место. Дайте два!
Вопросы и ответы на RSDN. Что это такое lvalue и rvalue?
19.11.2017
20:13
Автор: Павел Кузнецов
Текстовые метки: c++, lvalue, rvalue
Раздел: Компьютер - Программирование - Язык C++

Что это такое lvalue и rvalue?


Чтобы получить представление о понятиях lvalue и rvalue, проще всего рассмотреть, как выглядит некая переменная с точки зрения компилятора, так сказать "изнутри".


int i = 10;


С переменной i связаны две вещи: адрес и значение объекта, который она обозначает. Пусть, для определенности, адрес i — 0x1000. Значение, помещенное по этому адресу, как мы видим, — число 10. Незаметно для нас компилятор попеременно использует то одно, то другое из этих двух чисел. Например:


int j;

j = i;


В этом месте компилятор должен сгенерировать команду (или последовательность команд), которая означает следующее: «извлечь значение переменной i (число 10, записанное по адресу 0x1000) и записать его по адресу переменной j».

Или:


++i;


«Нарастить то, что находится по адресу переменной i (0x1000) на 1».


i = 20;


«Поместить по адресу 0x1000 значение 20».


В отличие от переменной i, с литералом 20 никакие адреса не связаны, только значение. В частности, выражения вида:


++20;

20 = 10;


никакого смысла в С или C++ не имеют. Таким образом, с любым выражением связаны либо адрес и значение, либо только значение.

Для того, чтобы отличать выражения, обозначающие объекты, от выражений, обозначающих только значения, ввели понятия lvalue и rvalue. Изначально слово lvalue использовалось для обозначения выражений, которые могли стоять слева от знака присваивания (left-value); им противопоставлялись выражения, которые могли находиться только справа от знака присваивания (right-value). Развитие языков C и C++ привело к утрате словом lvalue своего первоначального значения. Иногда lvalue трактуют также как locator value.

С каждой функцией компилятор также связывает две вещи: ее адрес и ее тело («значение»). При необходимости выражение, обозначающее функцию, компилятор может привести к указателю на эту функцию. «Значениями» функций в языках C и C++ оперировать нельзя. В спецификации языка C++ термин lvalue относится также и к выражениям, обознающим функции. В стандарте языка C для выражений, обозначающих функции, используется отдельный термин — functiondesignator.

Необходимо подчеркнуть, что lvalue/rvalue является свойством не объектов и/или функций, а выражений. Например:


char a [10];


i — lvalue

++i — lvalue

*&i — lvalue

a[5] — lvalue

a[i] — lvalue


однако:


10 — rvalue

i + 1 — rvalue

i++ — rvalue


modifiable lvalues, non-modifiable lvalues

Не все выражения, являющиеся lvalue, могут быть использованы для модификации обозначаемых ими объектов. Например, если t определена как "const int& t = i;", выражение t, будучи lvalue, не может быть использовано для модификации объекта, который оно обозначает. Такие выражения называют non-modifiable lvalues; им противопоставляют modifiablelvalues, которые могут быть использованы для модификации обозначаемых ими объектов.

lvalue-to-rvalue conversion

Очевидно, что если мы будем использовать любое из приведенных ранее lvalue-выражений в контексте, требующем значения объекта (например, в правой части операции присваивания), использование компилятором адресов будет ошибкой. Т.е., хотя выражение i является lvalue ("адрес и значение"), компилятор должен использовать соответствующее rvalue ("значение"). В терминах стандарта C++ это называется преобразованием lvalue к rvalue (lvalue-to-rvalue conversion).

Критерии lvalue/rvalue

В качестве критерия того, является ли некоторое выражение e lvalue, часто предлагают использовать возможность помещения этого выражения по левую сторону от знака присваивания, т.е. если выражение "e = . . ." допустимо, то e — lvalue. Однако этот критерий «не работает». Во-первых он не «пропускает» некоторые lvalues. Например, хотя выражения, состоящие только из имени массива или функции, являются lvalue, они не могут стоять по левую сторону от '='. Во-вторых он может ложно «диагностировать» некоторые rvalues как lvalues. Например, в C++ для rvalues классов можно вызывать функции члены, поэтому некоторые rvalues вполне могут находиться слева от '=':


class C { };


int main()

{

C() = C(); // OK

return 0;

}


Данная программа, хотя и не слишком осмысленна, вполне «законна» с точки зрения спецификации языка C++. При этом выражение С(), стоящее слева от '=', по правилам языка C++ является rvalue,

Одним из более успешно «работающих» критериев может служить возможность применения к выражению операции взятия адреса, т.е. если выражение "&e" допустимо, то e — lvalue. В частности, этот критерий правильно «срабатывает» для большинства временных объектов и non-modifiable lvalues. Однако, в C++ и здесь не все гладко: если в классе C переопределена операция взятия адреса (унарная операция &) и при этом она помещена в секцию private, вне определения класса C или его «друзей» к lvalue-выражению, обозначающему объект типа C, унарная операция & будет неприменима:


class C {

private:

C* operator &() { return this; }

};


int main()

{

C c;

&c; // ERRORreturn 0;

}


И наоборот: если унарная операция & определена в секции public, к выражению C(), являющемуся rvalue, можно будет применять унарную операцию &:


class C

{

public:

C* operator &() { return this; }

};


int main()

{

&C(); // OK

return 0;

}


В принципе, возможность применения к выражению встроенной операции получения адреса (унарная операция &) можно считать достаточным критерием того, что выражение является lvalue.

При наличии сомнений следует руководствоваться спецификацией языка. Вот перечисление некоторых характерных случаев:

rvalue:

  • Выражения, обозначающие временные объекты. В частности, результат вызова функций, возвращающих объекты не по ссылке; результат встроенных операций +, -. *, / и т.п.; явное создание временной переменной int() или C(); преобразования не к ссылочным типам и т.д.
  • Результат встроенной операции взятия адреса (&) — rvalue типа указатель.
  • Результат встроенных постфиксных операций ++, --.
  • Литералы за исключением строковых.
  • Константы перечислений.

lvalue:

  • Выражения, непосредственно обозначающие объект, non-modifiable в случае const-квалификации. Например, имя переменной, параметра функции и т.п.
  • Выражения ссылочных типов. non-modifiable в случае const-квалификации. В частности, результат вызова функций, возвращающих объекты по ссылке; выражения, состоящие из имен ссылочных переменных; операции преобразования к ссылочному типу и т.д.
  • Результат встроенной операции разыменования (*) — lvalue указуемого типа; non-modifiable в случае const-квалификации.
  • Результат встроенных префиксных операций ++, --.
  • Имя функции — non-modifiable lvalue; может быть преобразовано к rvalue «указатель на функцию».
  • Имя массива — non-modifiable lvalue; может быть преобразовано к rvalue «указатель на первый элемент массива».
  • Строковые литералы — non-modifiable lvalue; может быть преобразовано к rvalue «указатель на char/wchar_t».


Так же в этом разделе:
 
MyTetra Share v.0.52
Яндекс индекс цитирования