Сообщений 51 Оценка 529 [+3/-0] Оценить |
Чтобы получить представление о понятиях 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 |
Не все выражения, являющиеся lvalue, могут быть использованы для модификации обозначаемых ими объектов. Например, если t определена как "const int& t = i;", выражение t, будучи lvalue, не может быть использовано для модификации объекта, который оно обозначает. Такие выражения называют non-modifiable lvalues; им противопоставляют modifiablelvalues, которые могут быть использованы для модификации обозначаемых ими объектов.
Очевидно, что если мы будем использовать любое из приведенных ранее lvalue-выражений в контексте, требующем значения объекта (например, в правой части операции присваивания), использование компилятором адресов будет ошибкой. Т.е., хотя выражение i является lvalue ("адрес и значение"), компилятор должен использовать соответствующее rvalue ("значение"). В терминах стандарта C++ это называется преобразованием lvalue к rvalue (lvalue-to-rvalue conversion).
В качестве критерия того, является ли некоторое выражение 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(); // OKreturn 0; } |
В принципе, возможность применения к выражению встроенной операции получения адреса (унарная операция &) можно считать достаточным критерием того, что выражение является lvalue.
При наличии сомнений следует руководствоваться спецификацией языка. Вот перечисление некоторых характерных случаев:
rvalue:
lvalue:
Сообщений 51 Оценка 529 [+3/-0] Оценить |