Что такое rvalues, lvalues, xvalues, glvalues и prvalues?

1078

В С++ 03 выражение имеет значение rvalue или lvalue.

В С++ 11 выражение может быть:

  • Rvalue
  • именующего
  • xvalue
  • glvalue
  • prvalue

Две категории стали пятью категориями.

  • Что представляют собой эти новые категории выражений?
  • Как эти новые категории относятся к существующим категориям rvalue и lvalue?
  • Являются ли категории rvalue и lvalue в С++ 0x такими же, как в С++ 03?
  • Почему нужны эти новые категории? Являются ли боги WG21 просто путают нас простых смертных?
  • 0
    Являются ли rvalue и lvalue взаимоисключающими? Выражение x где x - это int может использоваться как l-значение или как r-значение.
  • 9
    @Philip Поттер: В C ++ 03? Да. Lvalue может использоваться в качестве rvalue, потому что существует стандартное преобразование lvalue в rvalue.
Показать ещё 12 комментариев
Теги:
c++11
expression
c++-faq

11 ответов

569

Я предполагаю, что этот документ может стать не столь коротким введением: n3055

Вся резня началась с семантики движения. Как только у нас есть выражения, которые можно перемещать и не копировать, внезапно легко понять правила, требующие различия между выражениями, которые можно перемещать, и в каком направлении.

Из того, что я думаю, основываясь на проекте, различие значений r/l остается неизменным, только в контексте перемещения вещей становится беспорядочным.

Они нужны? Наверное, нет, если мы хотим потерять новые возможности. Но чтобы обеспечить лучшую оптимизацию, мы должны, вероятно, охватить их.

Цитата n3055:

  • lvalue (так называемый, исторически, потому что lvalues ​​могут появляться на левая часть задания выражение) обозначает функцию или объект. [Пример: если E является выражение типа указателя, затем *E является выражением lvalue, относящимся к объект или функцию, к которой E точки. В качестве другого примера результат вызова функции, чья Тип возврата - это значение lvalue lvalue.]
  • xvalue ( Значение "eXpiring" также относится к объект, обычно ближе к концу (так что его ресурсы могут например, перемещаются). Значение x равно результат определенных видов выражения, содержащие rvalue Рекомендации. [Пример: результат вызова функции, чья Тип возврата - это rvalue reference xvalue.]
  • glvalue ( "обобщенное" значение lvalue) lvalue или xvalue.
  • rvalue (так называемый, исторически, поскольку rvalues ​​могли появляются в правой части выражение присваивания) является значением x, временный объект или его подобъектом или значением, которое не связанный с объектом.
  • prvalue ( "чистое" rvalue) является rvalue это не значение x. [Пример: результат вызова функции, чья тип возврата не является ссылкой prvalue]

Документ, о котором идет речь, является отличной ссылкой на этот вопрос, поскольку он показывает точные изменения в стандарте, которые произошли в результате введения новой номенклатуры.

  • 0
    Спасибо, этот ответ действительно полезен! Но мой компилятор не согласен с вашими примерами для xvalues и prvalues; они полная противоположность. Возврат по rvalue-ссылке дает мне prvalue, а возврат по значению дает мне xvalue. Вы их перепутали, или мой испытательный стенд сломан? Я попробовал это с GCC 4.6.1, clang (из svn) и MSVC, и все они показывают одинаковое поведение.
  • 0
    Ой, я просто перешел по ссылке и заметил, что примеры есть в источнике. Я пойду найду свою копию стандарта и проверю, что там написано ...
Показать ещё 5 комментариев
308

Что представляют собой эти новые категории выражений?

FCD (n3092) имеет отличное описание:

- lvalue (так называемый, исторически, поскольку lvalues ​​могут появляться на левая часть задания выражение) обозначает функцию или объект. [Пример: если E является выражение типа указателя, тогда * E - выражение lvalue, относящееся к объекту или функции, к которым E точки. В качестве другого примера, результат вызова функции, возвращение которой type является ссылкой на lvalue именующий. -end пример]

- значение x (an Значение "eXpiring" также относится к объект, обычно ближе к концу (так что его ресурсы могут быть перемещен, например). Значение x является результат определенных видов выражений с использованием ссылок rvalue (8.3.2). [ Пример: результат вызова функция, возвращающий тип которой Ссылка rvalue - это значение x. -конец пример]

- Значение glvalue ( "обобщенное" lvalue) является lvalue или xvalue.

- Rvalue (так называемый, исторически, потому что rvalues ​​могут появиться на правая часть задания выражения) является x значением, временным объект (12.2) или его подобъект или значение, которое не связано с объект.

- PRvalue ( "чистое" значение r) значение r, которое не является значением x. [ Пример: результат вызова функция, тип возврата которой не является ссылка - это prvalue. Значение a буквальный, такой как 12, 7.3e5, или true также prvalue. -end пример]

Каждый выражение принадлежит точно одному из фундаментальные классификации в эта таксономия: lvalue, xvalue или prvalue. Это свойство выражение называется его значением категория. [Примечание: обсуждение каждый встроенный оператор в разделе 5 указывает категорию ее ценности доходности и категории значений операндов, которых он ожидает. Например, встроенные операторы присваивания ожидают что левый операнд является lvalue и что правый операнд является prvalue и в результате получим lvalue. Определяемые пользователем операторы - это функции, и категории ценностей, которые они ожидание и доходность определяются их параметров и типов возврата. -конец примечание

Я предлагаю вам прочитать весь раздел 3.10 Lvalues ​​и rvalues ​​, хотя.

Как эти новые категории относятся к существующим категориям rvalue и lvalue?

Снова:

Изображение 1186

Являются ли категории rvalue и lvalue в С++ 0x такими же, как и в С++ 03?

Семантика rvalues ​​развилась особенно с введением семантики перемещения.

Почему нужны эти новые категории?

Таким образом, конструкция/назначение перемещения можно определить и поддерживать.

  • 45
    Мне нравится схема здесь. Я думаю, что было бы полезно начать ответ с «Каждое выражение принадлежит точно к одной из фундаментальных классификаций в этой таксономии: lvalue, xvalue или prvalue». Тогда легко использовать диаграмму, чтобы показать, что эти три фундаментальных класса объединены в glvalue и rvalue.
  • 0
    "is glvalue" эквивалентно "is not prvalue", а "is rvalue" эквивалентно "is not lvalue".
Показать ещё 1 комментарий
162

Я начну с вашего последнего вопроса:

Почему нужны эти новые категории?

Стандарт С++ содержит множество правил, относящихся к категории значений выражения. В некоторых правилах проводится различие между значениями lvalue и rvalue. Например, когда дело доходит до разрешения перегрузки. Другие правила делают различие между glvalue и prvalue. Например, вы можете иметь glvalue с неполным или абстрактным типом, но не существует prvalue с неполным или абстрактным типом. Прежде, чем мы получили эту терминологию, правила, которые действительно должны различать glvalue/prvalue, относящиеся к lvalue/rvalue, и они либо были непреднамеренно неправильными, либо содержали множество объяснений и исключений из правила a la "... если значение r должно быть вызвано неназванным rvalue reference...". Таким образом, кажется хорошей идеей просто дать понятия glvalues ​​и prvalues ​​собственное имя.

Что это за новые категории выражений? Как эти новые категории относятся к существующим категориям rvalue и lvalue?

Мы все еще имеем слова lvalue и rvalue, которые совместимы с С++ 98. Мы просто разделили rvalues ​​на две подгруппы, xvalues ​​и prvalues, и мы будем называть lvalues ​​и xvalues ​​как glvalues. Xvalues ​​являются новой категорией значений для неназванных ссылок rvalue. Каждое выражение является одним из трех: lvalue, xvalue, prvalue. Диаграмма Венна будет выглядеть так:

    ______ ______
   /      X      \
  /      / \      \
 |   l  | x |  pr  |
  \      \ /      /
   \______X______/
       gl    r

Примеры с функциями:

int   prvalue();
int&  lvalue();
int&& xvalue();

Но также не забывайте, что названные ссылки rvalue являются lvalues:

void foo(int&& t) {
  // t is initialized with an rvalue expression
  // but is actually an lvalue expression itself
}
143

Почему нужны эти новые категории? Являются ли боги WG21 просто путают нас простых смертных?

Я не чувствую, что другие ответы (хорошо, хотя многие из них) действительно фиксируют ответ на этот конкретный вопрос. Да, эти категории и такие существуют, чтобы позволить семантику перемещения, но сложность существует по одной причине. Это одно нерушимое правило перемещения вещей в С++ 11:

Ты должен двигаться только тогда, когда это, безусловно, это безопасно.

Вот почему существуют эти категории: иметь возможность говорить о ценностях, где можно безопасно переходить от них, и говорить о том, где это не так.

В ранней версии ссылок r-value движение происходило легко. Слишком легко. Достаточно легко, что существует много возможностей для неявного перемещения вещей, когда пользователь на самом деле не имел в виду.

Вот обстоятельства, при которых безопасно что-то перемещать:

  1. Когда это временный или подобъект. (Prvalue)
  2. Когда пользователь явно сказал, чтобы переместить его.

Если вы это сделаете:

SomeType &&Func() { ... }

SomeType &&val = Func();
SomeType otherVal{val};

Что это делает? В более старых версиях спецификации до появления 5 значений это спровоцировало бы движение. Конечно. Вы передали ссылку rvalue на конструктор и, таким образом, привязываетесь к конструктору, который принимает ссылку rvalue. Это очевидно.

Там только одна проблема с этим; вы не просили его переместить. О, вы могли бы сказать, что && должно было быть ключом, но это не меняет того факта, что он нарушил правило. val не является временным, потому что временные имена не имеют имен. Возможно, вы продлевали срок службы временного, но это означает, что он не является временным; это как и любая другая переменная стека.

Если это не временное, и вы не просили его переместить, то перемещение происходит неправильно.

Очевидное решение состоит в том, чтобы сделать val значением lvalue. Это означает, что вы не можете перейти от него. Хорошо; он назвал, поэтому его значение.

Как только вы это сделаете, вы уже не можете сказать, что SomeType&& означает одно и то же. Теперь вы сделали различие между названными ссылками rvalue и неназванными ссылками rvalue. Ну, названные ссылки rvalue - lvalues; это было наше решение выше. Итак, что мы называем неназванными ссылками rvalue (возвращаемое значение из Func выше)?

Это не lvalue, потому что вы не можете перейти от lvalue. И мы должны иметь возможность двигаться, возвращая &&; как еще вы могли бы прямо сказать что-то переместить? Это то, что std::move возвращает, в конце концов. Это не rvalue (old-style), потому что это может быть в левой части уравнения (вещи на самом деле немного сложнее, см. Этот вопрос и комментарии ниже). Это ни lvalue, ни rvalue; это новый вид вещей.

У нас есть ценность, которую вы можете рассматривать как lvalue, за исключением того, что она неявно перемещается. Мы называем это значением x.

Обратите внимание, что xvalues - это то, что заставляет нас получать две другие категории значений:

  • Prvalue на самом деле просто новое имя для предыдущего типа rvalue, то есть они являются значениями, которые не являются значениями xvalues.

  • Glvalues - это объединение значений x и lvalues в одной группе, потому что они действительно имеют много общих свойств.

Так что действительно, все сводится к значениям x и необходимости ограничивать движение точно и только в определенных местах. Эти места определяются категорией rvalue; prvalues - это неявные движения, а xvalues - явные перемещения (std::move возвращает значение xvalue).

  • 0
    Это интересно, но компилируется ли? Func у Func не должно быть оператора return?
  • 10
    @ Томас: это пример; не имеет значения, как он создает возвращаемое значение. Важно то, что он возвращает && .
Показать ещё 8 комментариев
100

IMHO, лучшее объяснение его значения дало нам Stroustrup + принять во внимание примеры Dániel Sándor и Мохан:

Страуструп:

Теперь я серьезно волновался. Ясно, что мы двинулись в тупик или беспорядок или и то, и другое. Я провел обеденное время, проведя анализ, чтобы посмотреть, какие свойств (значений) были независимыми. Было всего два независимые свойства:

  • has identity - то есть и адрес, указатель, пользователь может определить, идентичны ли две копии и т.д.
  • can be moved from - то есть нам разрешено оставить источник "копии" в некотором неопределенном, но действительном состоянии

Это привело меня к выводу, что существует ровно три вида значения (с использованием новационного трюка регулярного выражения с использованием заглавной буквы указывают на негатив - я спешил):

  • iM: имеет личность и не может быть перемещен из
  • iM: имеет личность и может быть перемещен из (например, результат литья lvalue в ссылку rvalue)
  • iM: не имеет идентификатора и может быть перенесен с четвертой возможности (iM: не имеет идентификатора и не может быть перемещен) не является полезно в C++ (или, я думаю) на любом другом языке.

В дополнение к этим трем фундаментальным классификациям значений мы имеют два очевидных обобщения, которые соответствуют двум независимые свойства:

  • i: имеет личность
  • m: можно перемещать из

Это заставило меня поставить эту диаграмму на доске: Изображение 1187

Нейминг

Я заметил, что у нас была только ограниченная свобода: два момента левые (обозначенные iM и i) - это люди с более или менее формальность назвали lvalues, а две точки справа (обозначенные m и iM) - это люди с большей или меньшей формальностью назвали rvalues. Это должно отразиться на нашем наименовании. То есть, левая "нога" W должна иметь имена, связанные с lvalue, и правая "нога" W должна иметь имена, связанные с rvalue.. Я отмечаю что вся эта дискуссия/проблема возникает из-за введения rvalue ссылки и перемещение семантики. Эти понятия просто не существуют в мире Стрэши, состоящем всего из rvalues и lvalues. Кто то заметил, что идеи, что

  • Каждый value является либо lvalue, либо rvalue
  • An lvalue не является rvalue, а rvalue не является lvalue

глубоко погружены в наше сознание, очень полезные свойства и следы этой дихотомии можно найти по всему проекту стандарта. Мы все согласились, что мы должны сохранить эти свойства (и сделать их точный). Это еще больше ограничило наши варианты именования. Я заметил, что стандартная редакция библиотеки использует rvalue для обозначения m ( обобщение), чтобы сохранить ожидания и текст стандартная библиотека, правая нижняя точка W должна быть названа rvalue.

Это привело к целенаправленному обсуждению именования. Во-первых, нам нужно было решить on lvalue. Если lvalue означает iM или обобщение i? Светодиод Дуг Грегор, мы перечислили места в формулировке основного языка где слово lvalue было квалифицировано как означающее тот или иной. список был сделан и в большинстве случаев и в самом сложном/хрупком тексте lvalue в настоящее время означает iM. Это классический смысл lvalue потому что "в старые времена" ничего не тронуло; move - новое понятие в C++0x. Кроме того, называние точки верхнего предела W lvalue дает нам свойство, что каждое значение является lvalue или rvalue, но не тем и другим.

Итак, верхняя левая точка W равна lvalue, а нижняя правая точка is rvalue. Что это делает нижние левые и верхние правые точки? Нижняя левая точка представляет собой обобщение классического значения lvalue, позволяя двигаться. Итак, это generalized lvalue. Мы назвали его glvalue. Вы можете спорить об аббревиатуре, но (я думаю) не с логикой. Мы предположили, что при серьезном использовании generalized lvalueкак-нибудь сократится, так что нам лучше это сделать немедленно (или смутить риск). Верхняя правая точка W меньше общий, чем нижний правый (теперь, как всегда, называется rvalue). Что точка представляет собой оригинальное чистое понятие объекта, который вы можете перемещать из-за того, что его нельзя ссылаться снова (кроме деструктора). Мне понравилась фраза specialized rvalue в отличие от generalized lvalue, но pure rvalue сокращено до prvalue выиграла (и вероятно, правильно). Итак, левая нога W равна lvalue и glvalue и правая нога prvalue и rvalue. Кстати, каждое значение - либо значение glvalue, либо prvalue, но не оба.

Это оставляет верхнюю середину W: iM; то есть значения, которые имеют идентичность и может быть перемещена. У нас действительно нет ничего, что поможет нас к доброму имени для этих эзотерических зверей. Они важны для люди, работающие со стандартным текстом (проект), но вряд ли стать фамилией. Мы не нашли реальных ограничений на называя нас руководством, поэтому мы выбрали 'x для центра, неизвестного, странный, только xpert или даже x-rated.

Изображение 1188

  • 11
    да, лучше читать оригинальные предложения и обсуждения комитета C ++, чем стандартные, если вы хотите понять, что они имели в виду: D
  • 4
    Литералы не имеют идентичности и не могут быть перемещены; они тем не менее полезны.
Показать ещё 2 комментария
35

Введение

ISOС++ 11 (официально ISO/IEC 14882: 2011) является самой последней версией стандарта языка программирования С++. Он содержит некоторые новые функции и концепции, например:

  • ссылки rvalue
  • значения значений xvalue, glvalue, prvalue
  • перемещение семантики

Если мы хотим понять концепции новых категорий значений выражения, мы должны знать, что существуют ссылки rvalue и lvalue. Лучше знать, что rvalues ​​могут быть переданы в неконстантные ссылки rvalue.

int& r_i=7; // compile error
int&& rr_i=7; // OK

Мы можем получить некоторую интуицию понятий категорий ценностей, если мы приводим подраздел под заголовками Lvalues ​​и rvalues ​​из рабочего проекта N3337 (самый похожий проект для опубликованного стандарта ISOС++ 11).

3.10 Lvalues ​​и rvalues ​​[basic.lval]

1 Выражения классифицируются в соответствии с таксономией на рисунке 1.

  • Значение l (так называемое, исторически, поскольку lvalues ​​может появиться в левой части выражения присваивания) обозначает функцию или объект. [Пример: если E является выражением типа указателя, тогда * E - выражение lvalue, относящееся к объекту или функции, к которой указывает E. В качестве другого примера, результат вызова функции чей тип возврата является ссылкой lvalue, является значением lvalue. -end пример]
  • Значение xvalue (значение "eXpiring" ) также относится к объекту, обычно ближе к концу его жизненного цикла (чтобы его ресурсы могли перемещаться, для пример). Значение x является результатом определенных видов выражений с использованием ссылок rvalue (8.3.2). [Пример: результат вызова функция, возвращающим тип которой является ссылкой rvalue, является значением x. -конец пример]
  • Значение glval ( "обобщенное" lvalue) является значением lvalue или значением x.
  • Значение rvalue (так называемое, исторически, поскольку rvalues ​​может отображаться в правой части выражения присваивания) является xvalue, a временный объект (12.2) или его подобъект или значение, которое не является связанные с объектом.
  • Значение prvalue ( "чистое" значение r) является значением r, которое не является значением x. [Пример: результат вызова функции, возвращаемый тип которой не является ссылка - это prvalue. Значение литерала, такого как 12, 7.3e5, или
    true также является prvalue. -end пример]

Каждое выражение принадлежит точно одному из фундаментальных классификации в этой таксономии: lvalue, xvalue или prvalue. Эта свойство выражения называется его категорией значений.

Но я не совсем уверен в том, что этого подраздела достаточно, чтобы понять понятия ясно, потому что "обычно" на самом деле не является общим, "ближе к концу его жизни" на самом деле не является конкретным, "с использованием ссылок rvalue" не является действительно ясно, и "Пример: результат вызова функции, тип возврата которой является ссылкой rvalue, является значением xvalue". звучит как змея, кусающая хвост.

КАТЕГОРЫ ПЕРВИЧНОГО ЗНАЧЕНИЯ

Каждое выражение относится к одной первичной категории значений. Эти категории значений - это значения lvalue, xvalue и prvalue.

lvalues ​​

Выражение E относится к категории lvalue тогда и только тогда, когда E относится к сущности, которая УЖЕ имела идентификатор (адрес, имя или псевдоним), который делает его доступным вне E.

#include <iostream>

int i=7;

const int& f(){
    return i;
}

int main()
{
    std::cout<<&"www"<<std::endl; // This address ...
    std::cout<<&"www"<<std::endl; // ... and this address are the same.
    "www"; // The expression "www" in this row is an lvalue expression, because it refers to the same entity ...
    "www"; // ... as the entity the expression "www" in this row refers to.

    i; // The expression i in this row is an lvalue expression, because it refers to the same entity ...
    i; // ... as the entity the expression i in this row refers to.

    int* p_i=new int(7);
    *p_i; // The expression *p_i in this row is an lvalue expression, because it refers to the same entity ...
    *p_i; // ... as the entity the expression *p_i in this row refers to.

    const int& r_I=7;
    r_I; // The expression r_I in this row is an lvalue expression, because it refers to the same entity ...
    r_I; // ... as the entity the expression r_I in this row refers to.

    f(); // The expression f() in this row is an lvalue expression, because it refers to the same entity ...
    i; // ... as the entity the expression f() in this row refers to.

    return 0;
}

xvalues ​​

Выражение E относится к категории xvalue тогда и только тогда, когда оно

- результат вызова функции, неявно или явно, чей тип возврата является ссылкой rvalue на тип возвращаемого объекта или

int&& f(){
    return 3;
}

int main()
{
    f(); // The expression f() belongs to the xvalue category, because f() return type is an rvalue reference to object type.

    return 0;
}

- ссылка на ссылку rvalue для типа объекта или

int main()
{
    static_cast<int&&>(7); // The expression static_cast<int&&>(7) belongs to the xvalue category, because it is a cast to an rvalue reference to object type.
    std::move(7); // std::move(7) is equivalent to static_cast<int&&>(7).

    return 0;
}

- выражение доступа к члену класса, обозначающее нестатический элемент данных не ссылочного типа, в котором выражение объекта является значением x, или

struct As
{
    int i;
};

As&& f(){
    return As();
}

int main()
{
    f().i; // The expression f().i belongs to the xvalue category, because As::i is a non-static data member of non-reference type, and the subexpression f() belongs to the xvlaue category.

    return 0;
}

- выражение указателя на элемент, в котором первый операнд является значением x, а второй операнд является указателем на элемент данных.

Обратите внимание, что эффект приведенных выше правил заключается в том, что именованные ссылки rvalue для объектов рассматриваются как lvalues, а неназванные ссылки rvalue на объекты рассматриваются как xvalues; Ссылки rvalue на функции обрабатываются как lvalues, именованные или нет.

#include <functional>

struct As
{
    int i;
};

As&& f(){
    return As();
}

int main()
{
    f(); // The expression f() belongs to the xvalue category, because it refers to an unnamed rvalue reference to object.
    As&& rr_a=As();
    rr_a; // The expression rr_a belongs to the lvalue category, because it refers to a named rvalue reference to object.
    std::ref(f); // The expression std::ref(f) belongs to the lvalue category, because it refers to an rvalue reference to function.

    return 0;
}

prvalues ​​

Выражение E принадлежит к категории prvalue тогда и только тогда, когда E не принадлежит ни к значению, ни к категории значений x.

struct As
{
    void f(){
        this; // The expression this is a prvalue expression. Note, that the expression this is not a variable.
    }
};

As f(){
    return As();
}

int main()
{
    f(); // The expression f() belongs to the prvalue category, because it belongs neither to the lvalue nor to the xvalue category.

    return 0;
}

КАТЕГОРЫ MIXED VALUE

Существуют две дополнительные важные категории смешанных значений. Эти категории значений представляют собой категории rvalue и glvalue.

rvalues ​​

Выражение E относится к категории rvalue тогда и только тогда, когда E принадлежит категории xvalue или к категории prvalue.

Обратите внимание, что это определение означает, что выражение E относится к категории rvalue тогда и только тогда, когда E относится к объекту, у которого не было никакого идентификатора, что делает его доступным вне E YET.

glvalues ​​

Выражение E принадлежит к категории glvalue тогда и только тогда, когда E принадлежит категории lvalue или к категории значений x.

ПРАКТИЧЕСКОЕ ПРАВИЛО

Скотт Мейер опубликовал очень полезное эмпирическое правило, чтобы отличать rvalues ​​от lvalues.

  • Если вы можете взять адрес выражения, выражение будет lvalue.
  • Если тип выражения является ссылкой на lvalue (например, T & или const T & и т.д.), это выражение является lvalue.
  • В противном случае выражение является rvalue. Концептуально (и, как правило, также и на самом деле), rvalues ​​соответствуют временным объектам, таким как те, которые возвращаются из функций или создаются с помощью неявного типа преобразования. Большинство литеральных значений (например, 10 и 5.3) также являются значениями.
  • 0
    Я все еще не получаю gvalue :(, это помогло бы, если бы вы предоставили отличные примеры, как вы сделали для других категорий :)
  • 3
    Все примеры для lvalues и все примеры для xvalues также являются примерами для glvalues. Спасибо за редактирование!
Показать ещё 4 комментария
31
Категории

С++ 03 слишком ограничены, чтобы правильно вносить ссылки rvalue в атрибуты выражений.

С их введением было сказано, что ссылка на неназванный rvalue оценивается как rvalue, так что разрешение перегрузки будет предпочтительнее привязки ссылок rvalue, что сделает его выборными конструкторами перемещения над конструкторами копирования. Но было обнаружено, что это вызывает проблемы вокруг, например, с Динамические типы и с квалификацией.

Чтобы показать это, рассмотрим

int const&& f();

int main() {
  int &&i = f(); // disgusting!
}

В предварительных черновиках это было разрешено, потому что в С++ 03 значения типов неклассов никогда не имеют квалификации. Но предполагается, что const применяется в случае с rvalue-reference, потому что здесь мы ссылаемся на объекты (= память!), А drop const из неклассических значений r главным образом по той причине, что вокруг объекта нет объекта.

Проблема для динамических типов имеет сходный характер. В С++ 03 значения типа класса имеют известный динамический тип - это статический тип этого выражения. Потому что, чтобы иметь это по-другому, вам нужны ссылки или различия, которые оцениваются до значения lvalue. Это неверно с неназванными ссылками rvalue, но они могут демонстрировать полиморфное поведение. Чтобы решить эту проблему,

  • ссылки без имени rvalue становятся xvalues ​​. Они могут быть квалифицированными и потенциально отличаться от их динамического типа. Они, как и предполагалось, предпочитают ссылки rvalue во время перегрузки и не свяжутся с неконстантными ссылками lvalue.

  • То, что раньше было rvalue (литералы, объекты, созданные приведениями к типам без ссылки), теперь становится prvalue. Они имеют то же предпочтение, что и значения x во время перегрузки.

  • То, что ранее было lvalue, остается lvalue.

И две группы выполняются для захвата тех, которые могут быть квалифицированы и могут иметь разные динамические типы (glvalues ​​) и те, где перегрузка предпочитает привязку ссылки rvalue ( rvalues ​​).

  • 1
    ответ очевидно разумный. xvalue - это просто rvalue, который может быть cv-квалифицированным и динамически типизированным!
24

Я долгое время боролся с этим, пока не наткнулся на объяснение cppreference.com категорий .

Это на самом деле довольно просто, но я нахожу, что его часто объясняют так, что трудно запомнить. Здесь это объясняется очень схематично. Я приведу некоторые части страницы:

Основные категории

Категории первичных значений соответствуют двум свойствам выражений:

  • имеет личность: можно определить, относится ли выражение к тому же объекту, что и другое выражение, например, путем сравнения адресов объектов или функций, которые они идентифицируют (получаются прямо или косвенно);

  • можно перенести из: move constructor, move assign operator или другую перегрузку функции, которая реализует семантику перемещения, может привязываться к выражению.

Выражения, которые:

  • имеют идентификатор и не могут быть перенесены из них, называются выражениями lvalue;
  • имеют личность и могут быть перемещены из называются выражениями xvalue;
  • не имеют идентификатора и могут быть перемещены из вызываемых выражений prvalue;
  • не имеют идентификатора и не могут быть перемещены из него.

именующего

выражение lvalue ( "left value" ) является выражением, которое имеет идентичность и не может быть перемещено.

rvalue (до С++ 11), prvalue (начиная с С++ 11)

выражение prvalue ( "pure rvalue" ) - это выражение, которое не имеет идентификатора и может быть перенесено из.

xvalue

Выражение xvalue ( "expired value" ) является выражением, которое имеет идентификатор и может быть перемещено из.

glvalue

выражение glvalue ( "generalized lvalue" ) является выражением, которое является либо значением lvalue, либо значением x. Он имеет идентичность. Он может быть или не быть перемещен из.

rvalue (поскольку С++ 11)

выражение rvalue ( "right value" ) является выражением, которое является либо значением prvalue, либо значением x. Его можно перенести. Он может иметь или не иметь идентичности.

  • 1
    В некоторых книгах показано, что xvalues имеют свои x от «expert» или «extra»
  • 0
    И что еще более важно, их всеобъемлющий список примеров.
15

Как эти новые категории относятся к существующим категориям rvalue и lvalue?

A С++ 03 lvalue по-прежнему является значением С++ 11 lvalue, тогда как значение С++ 03 rvalue называется prvalue в С++ 11.

13

Одно дополнение к превосходным ответам выше, на том, что меня смутило даже после того, как я прочитал "Страуструп" и подумал, что я понимаю разницу в значении/значении. Когда вы видите

int&& a = 3,

очень заманчиво читать int&& как тип и заключить, что a является значением r. Это не так:

int&& a = 3;
int&& c = a; //error: cannot bind 'int' lvalue to 'int&&'
int& b = a; //compiles

a имеет имя и ipso facto lvalue. Не думайте о && как о части a; это просто что-то говорит вам, к чему a разрешено связывать.

Это особенно важно для аргументов типа T&& в конструкторах. Если вы пишете

Foo::Foo(T&& _t) : t{_t} {}

вы скопируете _t в t. Вам нужно

Foo::Foo(T&& _t) : t{std::move(_t)} {}, если вы хотите переместить. Если бы мой компилятор предупредил меня, когда я оставил move!

  • 1
    Я думаю, что этот ответ можно уточнить. «С чем разрешено связывать a »: Конечно, но в строках 2 и 3 ваши переменные - это c & b, и это не то, с чем связывается, и тип a здесь не имеет значения, не так ли? Строки будут одинаковыми, если a объявлено как int a . Фактическая основная разница состоит в том , что в строке 1 а , не должны быть const связываться с 3.
-2

В С++ переменные являются типом l-значения (произносится как ell-value). Значение l - это значение, которое имеет постоянный адрес (в памяти). Поскольку все переменные имеют адреса, все переменные являются значениями l. Имя l-значение возникло из-за того, что l-значения являются единственными значениями, которые могут быть в левой части оператора присваивания. Когда мы выполняем задание, левая часть оператора присваивания должна быть l-значением. Следовательно, утверждение типа 5 = 6; приведет к ошибке компиляции, поскольку 5 не является значением l. Значение 5 не имеет памяти, и, следовательно, ничто не может быть назначено ему. 5 означает 5, и его значение не может быть переназначено. Когда l-value имеет назначенное ему значение, текущее значение на этом адресе памяти перезаписывается.

Противоположностью l-значениям являются r-значения (выраженные arr-значения). Значение r относится к значениям, которые не связаны с постоянным адресом памяти. Примерами r-значений являются одиночные числа (например, 5, которые оцениваются до 5) и выражения (например, 2 + x, который оценивает значение переменной x плюс 2). r-значения обычно носят временный характер и отбрасываются в конце заявления, в котором они встречаются.

Ниже приведен пример некоторых операторов присваивания, показывающих, как оцениваются значения r:

int y;      // define y as an integer variable
y = 4;      // r-value 4 evaluates to 4, which is then assigned to l-value y
y = 2 + 5;  // r-value 2 + r-value 5 evaluates to r-value 7, which is then assigned to l-value y

int x;      // define x as an integer variable
x = y;      // l-value y evaluates to 7 (from before), which is then assigned to l-value x.
x = x;      // l-value x evaluates to 7, which is then assigned to l-value x (useless!)
x = x + 1;  // l-value x + r-value 1 evaluate to r-value 8, which is then assigned to l-value x.

Давайте рассмотрим последний оператор присваивания выше, поскольку он вызывает наибольшую путаницу.

x = x+1

В этом утверждении переменная x используется в двух разных контекстах. В левой части оператора присваивания "x" используется как l-значение (переменная с адресом), в котором сохраняется значение. С правой стороны оператора присваивания x вычисляется для получения значения (в данном случае 7). Когда С++ оценивает вышеуказанный оператор, он оценивается как:

x = 7+1

Что делает очевидным, что С++ присваивает значение 8 обратно переменной x.

В настоящее время вам не нужно много беспокоиться о значениях l или значениях r, но лучше вернуться к ним позже, когда мы начнем обсуждение более продвинутых тем.

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню