В C ++ 03 выражение является либо значением r, либо значением l .
В C ++ 11 выражение может быть:
- Rvalue
- именующий
- xvalue
- glvalue
- prvalue
Две категории стали пятью категориями.
- Каковы эти новые категории выражений?
- Как эти новые категории связаны с существующими категориями rvalue и lvalue?
- Являются ли категории rvalue и lvalue в C ++ 0x такими же, как в C ++ 03?
- Зачем нужны эти новые категории? Являются WG21 боги просто пытаются запутать нас , простых смертных?
c++
expression
c++-faq
c++11
Джеймс МакНеллис
источник
источник
string("hello") = string("world")
.Ответы:
Я думаю, этот документ может послужить не очень кратким введением: n3055
Вся резня началась с семантики переезда. Когда у нас есть выражения, которые можно перемещать, а не копировать, внезапно легко понять правила потребовали разграничения между выражениями, которые можно перемещать, и в каком направлении.
Из того, что я предполагаю на основании черновика, различие значений r / l остается тем же самым, только в контексте движущихся вещей становится грязным.
Они нужны? Вероятно, нет, если мы хотим отказаться от новых функций. Но для лучшей оптимизации нам, вероятно, следует принять их.
Цитирование n3055 :
E
является выражением типа указателя, тогда*E
является выражением lvalue, относящимся к объекту или функции, на которыеE
указывает. В качестве другого примера, результатом вызова функции, тип возвращаемой которой является ссылкой на lvalue, является lvalue.]Данный документ является отличной ссылкой на этот вопрос, потому что он показывает точные изменения в стандарте, которые произошли в результате введения новой номенклатуры.
источник
FCD (n3092) имеет превосходное описание:
Я предлагаю вам прочитать весь раздел 3.10 Lvalues и rvalues .
Снова:
Семантика rvalues эволюционировала особенно с введением семантики перемещения.
Так что конструкция / назначение перемещения могут быть определены и поддержаны.
источник
glvalue
какlvalue
иlvalue
какplvalue
, чтобы быть последовательным?Я начну с вашего последнего вопроса:
Стандарт C ++ содержит много правил, которые имеют дело с категорией значений выражения. Некоторые правила проводят различие между lvalue и rvalue. Например, когда дело доходит до разрешения перегрузки. Другие правила делают различие между glvalue и prvalue. Например, у вас может быть glvalue с неполным или абстрактным типом, но не существует prvalue с неполным или абстрактным типом. До того, как у нас появилась эта терминология, правила, которые фактически должны различать glvalue / prvalue, относились к lvalue / rvalue, и они были либо непреднамеренно неправильными, либо содержали множество объяснений и исключений из правила а-ля "... если только значение rvalue не связано с неназванным Значение ссылки ... ». Таким образом, кажется хорошей идеей просто дать понятиям glvalues и prvalues их собственное имя.
У нас все еще есть термины lvalue и rvalue, которые совместимы с C ++ 98. Мы просто разделили rvalues на две подгруппы, xvalues и prvalues, и мы называем lvalues и xvalues как glvalues. Xvalues - это новый тип категории значений для безымянных ссылок на rvalue. Каждое выражение является одним из этих трех: lvalue, xvalue, prvalue. Диаграмма Венна будет выглядеть так:
Примеры с функциями:
Но также не забывайте, что именованные ссылки на rvalue являются lvalues:
источник
Я не чувствую, что другие ответы (хотя и хорошие, хотя многие из них) действительно отражают ответ на этот конкретный вопрос. Да, эти категории и тому подобное существуют для обеспечения семантики перемещения, но сложность существует по одной причине. Это единственное нерушимое правило перемещения вещей в C ++ 11:
Ты будешь двигаться только тогда, когда это несомненно безопасно.
Вот почему существуют эти категории: уметь говорить о ценностях, куда безопасно перейти от них, и говорить о ценностях, которых нет.
В самой ранней версии ссылок на r-значения движение происходило легко. Слишком легко Достаточно легко, что был большой потенциал для неявного перемещения вещей, когда пользователь не хотел этого делать.
Вот обстоятельства, при которых можно что-то переместить:
Если вы делаете это:
Что это делает? В более старых версиях спецификации, до того, как 5 значений пришли, это спровоцировало бы движение. Конечно, это так. Вы передали ссылку rvalue в конструктор, и, таким образом, он связывается с конструктором, который принимает ссылку rvalue. Это очевидно.
Есть только одна проблема с этим; Вы не просили переместить это. О, вы можете сказать, что это
&&
должно было быть ключом, но это не меняет того факта, что это нарушило правило.val
не временный, потому что временные имена не имеют. Возможно, вы продлили срок действия временного, но это означает, что он не является временным ; это как любая другая переменная стека.Если это не временно, и вы не просили его переместить, значит, переезд - это неправильно.
Очевидное решение - сделать
val
lvalue. Это означает, что вы не можете отойти от этого. Хорошо; это имя, так что это lvalue.Как только вы это сделаете, вы уже не сможете сказать, что это
SomeType&&
означает то же самое, что и везде. Теперь вы сделали различие между именованными ссылками rvalue и безымянными ссылками rvalue. Ну, именованные ссылки rvalue являются lvalues; это было наше решение выше. Итак, что мы называем безымянными ссылками rvalue (возвращаемое значениеFunc
сверху)?Это не lvalue, потому что вы не можете перейти от lvalue. И мы должны быть в состоянии двигаться, возвращая
&&
; как еще вы могли бы явно сказать, чтобы переместить что-то? Это тоstd::move
, что возвращается, в конце концов. Это не rvalue (в старом стиле), потому что он может быть в левой части уравнения (на самом деле все немного сложнее, см. Этот вопрос и комментарии ниже). Это не lvalue или rvalue; это новая вещь.У нас есть значение, которое вы можете рассматривать как lvalue, за исключением того, что оно неявно перемещаемо из. Мы называем это xvalue.
Обратите внимание, что xvalues - это то, что заставляет нас получить другие две категории значений:
Prvalue - это на самом деле просто новое имя для предыдущего типа rvalue, то есть это значения, которые не являются значениями xvalue.
Glvalues - это объединение xvalues и lvalues в одну группу, потому что они имеют много общих свойств.
Так что на самом деле все сводится к xvalues и необходимости ограничивать движение точно и только в определенных местах. Эти места определяются категорией rvalue; prvalue - это неявные ходы, а xvalue - это явные ходы (
std::move
возвращает xvalue).источник
&&
.X foo(); foo() = X;
... По этой фундаментальной причине я не могу полностью следовать вышеприведенному превосходному ответу до конца, потому что вы действительно делаете различие только между новое xvalue и prvalue старого стиля, основанные на том факте, что оно может быть на lhs.X
быть классом;X foo();
быть объявлением функции, иfoo() = X();
быть строкой кода. (Я оставил второй набор скобок вfoo() = X();
своем комментарии выше.) Вопрос, который я только что опубликовал с выделенным этим использованием, см. На stackoverflow.com/questions/15482508/…ИМХО, лучшее объяснение его значения дало нам Stroustrup + принять во внимание примеры Даниэля Шандора и Мохана :
Страуструп:
источник
lvalue
s, все остальные литералы - этоprvalue
s. Строго говоря, вы можете привести аргумент в пользу того, что нестроковые литералы должны быть неподвижными, но стандарт написан не так.ВВЕДЕНИЕ
ISOC ++ 11 (официально ISO / IEC 14882: 2011) является самой последней версией стандарта языка программирования C ++. Он содержит некоторые новые функции и концепции, например:
Если мы хотим понять концепцию новых категорий значений выражений, мы должны знать, что существуют ссылки на rvalue и lvalue. Лучше знать, что значения могут быть переданы неконстантным ссылкам.
Мы можем получить некоторое представление о концепциях категорий значений, если процитируем подраздел «Значения и значения» из рабочего проекта N3337 (наиболее похожего на опубликованный стандарт ISOC ++ 11).
Но я не совсем уверен в том, что этого подраздела достаточно для четкого понимания концепций, потому что «обычно» не является действительно общим, «ближе к концу его жизненного цикла» не совсем конкретно, «использование ссылок на значения» не совсем понятно, и «Пример. Результатом вызова функции, тип возвращаемой которой является ссылкой на rvalue, является значение xvalue». Похоже, змея кусает свой хвост.
ПЕРВИЧНАЯ ЦЕННОСТЬ КАТЕГОРИИ
Каждое выражение принадлежит ровно одной категории первичных значений. Этими категориями значений являются категории lvalue, xvalue и prvalue.
lvalues
Выражение E относится к категории lvalue тогда и только тогда, когда E относится к объекту, у которого УЖЕ есть идентификатор (адрес, имя или псевдоним), который делает его доступным вне E.
xvalues
Выражение E относится к категории xvalue тогда и только тогда, когда оно
- результат вызова функции, неявной или явной, чей тип возвращаемого значения является ссылкой на тип возвращаемого объекта, или
- приведение к rvalue-ссылке на тип объекта, или
- выражение доступа к члену класса, обозначающее нестатический член данных не ссылочного типа, в котором объектное выражение является значением xvalue, или
- выражение указателя на член, в котором первый операнд является значением x, а второй операнд - указателем на элемент данных.
Обратите внимание, что эффект приведенных выше правил заключается в том, что именованные ссылки на значения rvalue обрабатываются как lvalues, а безымянные ссылки на значения rvalue на объекты обрабатываются как xvalues; rvalue ссылки на функции обрабатываются как lvalues независимо от того, названы они или нет.
prvalues
Выражение E относится к категории prvalue тогда и только тогда, когда E не принадлежит ни к lvalue, ни к категории xvalue.
КАТЕГОРИИ СМЕШАННЫХ ЦЕННОСТЕЙ
Есть еще две важные категории смешанных значений. Этими категориями значений являются категории rvalue и glvalue.
rvalues
Выражение E принадлежит категории rvalue тогда и только тогда, когда E принадлежит категории xvalue или категории prvalue.
Обратите внимание, что это определение означает, что выражение E принадлежит категории rvalue тогда и только тогда, когда E относится к объекту, у которого нет какой-либо идентичности, которая делает его доступным вне E YET.
glvalues
Выражение E принадлежит категории glvalue тогда и только тогда, когда E принадлежит категории lvalue или категории xvalue.
ПРАКТИЧЕСКОЕ ПРАВИЛО
Скотт Мейер опубликовал очень полезное практическое правило, позволяющее отличать значения от значений.
источник
struct As{void f(){this;}}
this
this
this
является prvalue, но*this
lvalue"www"
не всегда имеет один и тот же адрес. Это lvalue, потому что это массив .Категории C ++ 03 слишком ограничены, чтобы правильно отразить введение ссылок на rvalue в атрибуты выражений.
С введением их было сказано, что безымянная ссылка на rvalue оценивает значение rvalue, так что разрешение перегрузки предпочло бы привязки ссылки на rvalue, что заставило бы его выбирать конструкторы перемещения вместо конструкторов копирования. Но было обнаружено, что это вызывает проблемы повсюду, например, с динамическими типами и квалификациями.
Чтобы показать это, рассмотрим
В черновиках до xvalue это было разрешено, потому что в C ++ 03 r-значения не-классовых типов никогда не были cv-квалифицированными. Но предполагается , что
const
применяется в Rvalue-справочном случае, потому что здесь мы действительно относятся к объектам (= память!), И опуская сопзЬ из неклассовых rvalues в основном по той причине , что не существует ни одного объекта вокруг.Проблема для динамических типов имеет аналогичную природу. В C ++ 03 значения класса имеют известный динамический тип - это статический тип этого выражения. Потому что, чтобы это было по-другому, вам нужны ссылки или разыменования, которые приводят к lvalue Это не так с неназванными ссылками на rvalue, но они могут демонстрировать полиморфное поведение. Так что, чтобы решить это,
неназванные rvalue ссылки становятся xvalues . Они могут быть квалифицированы и потенциально могут иметь другой динамический тип. Они, как и предполагалось, предпочитают ссылки rvalue во время перегрузки и не будут привязываться к неконстантным ссылкам lvalue.
То, что раньше было rvalue (литералы, объекты, созданные путем приведения к не ссылочным типам), теперь становится prvalue . Они имеют то же предпочтение, что и xvalues во время перегрузки.
То, что раньше было lvalue, остается lvalue.
И две группы делаются для захвата тех, которые могут быть квалифицированы и могут иметь разные динамические типы ( glvalues ) и те, где перегрузка предпочитает привязку ссылки rvalue ( rvalues ).
источник
Я долго боролся с этим, пока не наткнулся на объяснение cppreference.com категорий значений .
На самом деле это довольно просто, но я нахожу, что это часто объясняется так, что его трудно запомнить. Здесь это объясняется очень схематично. Я процитирую некоторые части страницы:
источник
Значение C ++ 03 все еще остается значением C ++ 11, тогда как значение C ++ 03 называется prvalue в C ++ 11.
источник
Одно дополнение к превосходным ответам, приведенным выше, в момент, который смутил меня даже после того, как я прочитал Страуструпа и подумал, что понял различие между значением и значением. Когда ты видишь
int&& a = 3
,очень заманчиво читать
int&&
как тип и делать вывод, чтоa
это значение. Это не:a
имеет имя и является ipso facto lvalue. Не думайте об этом&&
как о части типаa
; это просто то, что говорит вам, чтоa
разрешено связывать.Это особенно важно для
T&&
аргументов типа в конструкторах. Если ты пишешьFoo::Foo(T&& _t) : t{_t} {}
вы будете копировать
_t
вt
. Тебе нужноFoo::Foo(T&& _t) : t{std::move(_t)} {}
если хочешь переехать. Будет ли мой компилятор предупреждать меня, когда я опущуmove
!источник
a
разрешено связывать»: Конечно, но в строках 2 и 3 ваши переменные - это c & b, и это не тот, который связывает, а типa
здесь не имеет значения, не так ли? Линии были бы такими же, еслиa
был объявленint a
. На самом деле главное отличие состоит в том, что в строке 1 a не обязательно указыватьconst
3.Поскольку предыдущие ответы исчерпывающе охватывали теорию, стоящую за категориями ценностей, я хотел бы добавить еще одну вещь: вы можете поиграть с ней и протестировать.
Для некоторых практических экспериментов с категориями значений вы можете использовать спецификатор decltype . Его поведение явно различает три категории основных значений (xvalue, lvalue и prvalue).
Использование препроцессора спасает нас от набора текста ...
Основные категории:
Смешанные категории:
Теперь мы можем воспроизвести (почти) все примеры из cppreference по категориям значений .
Вот несколько примеров с C ++ 17 (для краткого static_assert):
Смешанные категории становятся скучными, когда вы выяснили основную категорию.
Дополнительные примеры (и эксперименты) можно найти по следующей ссылке в проводнике компилятора . Не читайте сборку. Я добавил много компиляторов, чтобы убедиться, что он работает на всех распространенных компиляторах.
источник
#define IS_GLVALUE(X) IS_LVALUE(X) || IS_XVALUE(X)
самом деле следует#define IS_GLVALUE(X) (IS_LVALUE(X) || IS_XVALUE(X))
иначе посмотреть, что произойдет, если вы&&
двоеIS_GLVALUE
.