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

В C++03 выражение является либо значением r, либо значением l.

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

  1. Rvalue
  2. именующий
  3. xvalue
  4. glvalue
  5. prvalue

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

  • Каковы эти новые категории выражений?
  • Как эти новые категории связаны с существующими категориями rvalue и lvalue?
  • Являются ли категории rvalue и lvalue в C++0x такими же, как в C++03?
  • Зачем нужны эти новые категории? Боги WG21 просто пытаются сбить нас с толку простых смертных?

14 ответов

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

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

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

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

Цитирую n3055:

  • Lvalue (так называемый, исторически, потому что lvalue мог появиться в левой части выражения присваивания) обозначает функцию или объект. [Пример: если E является выражением типа указателя, то *E является выражением lvalue, относящимся к объекту или функции, к которой E точки. В качестве другого примера, результатом вызова функции, тип возвращаемой которой является ссылкой на lvalue, является lvalue.]
  • Значение xvalue (значение "eXpiring") также относится к объекту, обычно ближе к концу его времени жизни (например, чтобы его ресурсы могли быть перемещены). Xvalue - это результат некоторых видов выражений, включающих ссылки на rvalue. [Пример: результатом вызова функции, тип возвращаемой которой является ссылкой на rvalue, является xvalue.]
  • Glvalue ("обобщенное" lvalue) - это lvalue или xvalue.
  • Значение r (так называемое исторически, поскольку значения r могут находиться в правой части выражения присваивания) представляет собой значение x, временный объект или подобъект или значение, которое не связано с объектом.
  • Prvalue ("чистое" rvalue) - это rvalue, которое не является xvalue. [Пример: результат вызова функции, тип возврата которой не является ссылкой, является предварительным значением]

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

Каковы эти новые категории выражений?

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

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

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

- glvalue ("обобщенное" lvalue) - это lvalue или xvalue.

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

- prvalue ("чистое" rvalue) - это rvalue, которое не является xvalue. [Пример: результат вызова функции, тип возвращаемой которой не является ссылкой, является предварительным значением. Значение литерала, такого как 12, 7.3e5 или true, также является prvalue. - конец примера]

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

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

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

Снова:

таксономия

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

Семантика rvalues ​​эволюционировала, в частности, с введением семантики перемещения.

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

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

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

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

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

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

У нас все еще есть термины lvalue и rvalue, которые совместимы с C++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
}

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

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

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

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

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

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

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

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

SomeType &&Func() { ... }

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

Что это делает? В более старых версиях спецификации, до того, как 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; prvalues ​​- неявные ходы, а xvalues ​​- явные ходы (std::move возвращает значение x)

ИМХО, лучшее объяснение его значения дало нам Stroustrup + принять во внимание примеры Даниэля Шандора и Мохана:

Страуструп:

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

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

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

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

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

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

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

Именование

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

  • каждый value это либо lvalue или rvalue
  • lvalue это не rvalue и rvalue это не lvalue

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

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

Итак, верхняя левая точка W является lvalue и нижняя правая точка 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; то есть значения, которые имеют идентичность и могут быть перемещены. У нас действительно нет ничего, что помогло бы нам найти хорошее имя для тех эзотерических зверей. Они важны для людей, работающих с (черновым) стандартным текстом, но вряд ли станут нарицательным. Мы не нашли каких-либо реальных ограничений в именовании, чтобы направлять нас, поэтому мы выбрали "х" для центра, неизвестного, странного, только xpert или даже с рейтингом х.

Стив хвастается конечным продуктом

ВСТУПЛЕНИЕ

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

  • Rvalue ссылки
  • xvalue, glvalue, prvalue выражения значения категории
  • переместить семантику

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

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

Мы можем получить некоторое представление о концепциях категорий значений, если процитируем подраздел "Значения и значения" из рабочего проекта N3337 (наиболее похожего на опубликованный стандарт ISOC++ 11).

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

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

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

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

Но я не совсем уверен в том, что этого подраздела достаточно для четкого понимания концепций, потому что "обычно" не является действительно общим, "ближе к концу его жизненного цикла" не совсем конкретно, "использование ссылок на значения" не совсем понятно, и "Пример: результатом вызова функции, тип возвращаемой которой является ссылкой на 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 тогда и только тогда, когда оно

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

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;
}

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

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 не принадлежит ни к lvalue, ни к категории xvalue.

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;
}

КАТЕГОРИИ СМЕШАННЫХ ЦЕННОСТЕЙ

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

rvalues

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

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

glvalues

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

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

Скотт Мейер опубликовал очень полезное практическое правило, позволяющее отличать значения от значений.

  • Если вы можете взять адрес выражения, выражение будет lvalue.
  • Если тип выражения является ссылкой на lvalue (например, T& или const T& и т. Д.), Это выражение является lvalue.
  • В противном случае выражение является значением. Концептуально (и, как правило, также фактически), значения соответствуют временным объектам, таким как возвращаемые из функций или созданные посредством неявных преобразований типов. Большинство литеральных значений (например, 10 и 5.3) также являются r-значениями.

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

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

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

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

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

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

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

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

именующий

Выражение lvalue ("левое значение") - это выражение, которое имеет идентичность и не может быть перемещено из.

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

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

xvalue

Выражение xvalue ("expiring value") - это выражение, которое имеет идентичность и может быть перемещено из.

glvalue

Выражение glvalue ("обобщенное lvalue") - это выражение, которое является либо lvalue, либо xvalue. У него есть личность. Это может или не может быть перемещено из.

значение (начиная с C++ 11)

Выражение rvalue ("правильное значение") - это выражение, которое является либо prvalue, либо xvalue. Это может быть перемещено из. Он может иметь или не иметь идентичность.

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

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

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

int const&& f();

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

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

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

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

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

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

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

Это термины, которые комитет C++ использовал для определения семантики перемещения в C++ 11. Вот история .

Мне трудно понять термины, учитывая их точные определения, длинные списки правил или эту популярную диаграмму:

Это проще на диаграмме Венна с типичными примерами:

По сути:

  • каждое выражение является либо lvalue, либо rvalue
  • lvalue необходимо скопировать, потому что у него есть идентификатор, поэтому его можно использовать позже
  • rvalue можно переместить, потому что оно временное (prvalue) или явно перемещено (xvalue)

Теперь хороший вопрос: если у нас есть два ортогональных свойства («имеет идентичность» и «можно перемещать»), в какой четвертой категории следует заполнить lvalue, xvalue и prvalue? Это будет выражение, которое не имеет идентичности (следовательно, к нему нельзя будет получить доступ позже) и которое не может быть перемещено (необходимо скопировать его значение). Это просто бесполезно, поэтому не было названо.

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

Для некоторых практических экспериментов с категориями значений вы можете использовать спецификатор decltype. Его поведение явно различает три категории основных значений (xvalue, lvalue и prvalue).

Использование препроцессора спасает нас от набора текста...

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

#define IS_XVALUE(X) std::is_rvalue_reference<decltype((X))>::value
#define IS_LVALUE(X) std::is_lvalue_reference<decltype((X))>::value
#define IS_PRVALUE(X) !std::is_reference<decltype((X))>::value

Смешанные категории:

#define IS_GLVALUE(X) IS_LVALUE(X) || IS_XVALUE(X)
#define IS_RVALUE(X) IS_PRVALUE(X) || IS_XVALUE(X)

Теперь мы можем воспроизвести (почти) все примеры из cppreference по категориям значений.

Вот несколько примеров с C++17 (для краткого static_assert):

void doesNothing(){}
struct S
{
    int x{0};
};
int x = 1;
int y = 2;
S s;

static_assert(IS_LVALUE(x));
static_assert(IS_LVALUE(x+=y));
static_assert(IS_LVALUE("Hello world!"));
static_assert(IS_LVALUE(++x));

static_assert(IS_PRVALUE(1));
static_assert(IS_PRVALUE(x++));
static_assert(IS_PRVALUE(static_cast<double>(x)));
static_assert(IS_PRVALUE(std::string{}));
static_assert(IS_PRVALUE(throw std::exception()));
static_assert(IS_PRVALUE(doesNothing()));

static_assert(IS_XVALUE(std::move(s)));
// The next one doesn't work in gcc 8.2 but in gcc(trunk). Clang 7.0.0 and msvc 19.16 are doing fine.
static_assert(IS_XVALUE(S().x)); 

Смешанные категории становятся скучными, когда вы выяснили основную категорию.

Дополнительные примеры (и эксперименты) можно найти по следующей ссылке в проводнике компилятора. Не читайте сборку. Я добавил много компиляторов, чтобы убедиться, что он работает на всех распространенных компиляторах.

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

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

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

int&& a = 3,

очень заманчиво читать int&& как тип и сделать вывод, что a это значение. Это не:

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!

Это диаграмма Венна, которую я сделал для своей книги на C++ с высокой степенью визуализации, которую я скоро опубликую на Leanpub во время разработки.

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

Главный вывод по этой теме заключается в том, что выражения обладают двумя свойствами: идентичностью и подвижностью . Первый касается «прочности», с которой что-то существует. Это важно, потому что абстрактной машине C++ разрешено и поощряется агрессивно изменять и сжимать ваш код с помощью оптимизаций, а это означает, что вещи без идентичности могут существовать только в уме компилятора или в регистре в течение короткого момента, прежде чем они будут затоптаны на. Но такой фрагмент данных также гарантированно не вызовет проблем, если вы перерабатываете его внутренности, поскольку нет возможности попытаться его использовать. Таким образом, семантика перемещения была изобретена, чтобы позволить нам захватывать новые lvalue-ссылки на временные библиотеки, продлевая их время жизни.

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

В мире C++ идея «потребления» ресурса означает, что ресурс теперь принадлежит нам, и поэтому мы должны выполнить любую необходимую очистку и убедиться, что к объекту не будет доступа где-либо еще. Часто это означает, что нужно заимствовать их смелость для создания новых объектов. Я называю это «донорство органов». Обычно мы говорим об указателях или ссылках, содержащихся в объекте, или о чем-то подобном, и мы хотим сохранить эти указатели или ссылки, потому что они относятся к данным в другом месте нашей программы, которые не умирают.

Таким образом, вы можете написать перегрузку функции, которая принимает ссылку на rvalue, и если будет передано временное значение (prvalue), это будет вызвана перегрузка. Новое lvalue будет создано при привязке к ссылке rvalue, взятой функцией, что продлит жизнь временного объекта, чтобы вы могли использовать его в своей функции.

В какой-то момент мы поняли, что у нас часто есть невременные данные lvalue, с которыми мы закончили в одной области, но хотели каннибализировать в другой области. Но они не являются rvalue и поэтому не будут привязываться к ссылке rvalue. Итак, мы сделали std::move, который представляет собой просто причудливое преобразование от lvalue к ссылке rvalue. Такие данные - это xvalue: прежнее lvalue теперь действует так, как если бы оно было временным, поэтому его также можно переместить.

В языках без команды присваивания, таких как математика, значение выражения (например, x + 1) остается неизменным в пределах его области действия. Это важное свойство языка называется определенностью. Однако в языках с командой присваивания, таких как C++, значение выражения может варьироваться в пределах области действия присваивания; на самом деле места, участвующие в значении, остаются прежними, а их содержимое может меняться. Поэтому, чтобы сохранить определенность в этих языках, следует ввести новый тип значений: location .

По этой причине в 1967 году Кристофер Стрейчи ввел термины L-значение и R-значение в своем влиятельном наборе конспектов лекций « Фундаментальные концепции языков программирования» во время разработки языка программирования CPL:

  • L-значение : расположение в магазине автомата;
  • R-значение : значение (оно может быть содержимым местоположения и само по себе может быть местоположением).

Все выражения имеют R-значение, но только некоторые из них также имеют L-значение:

  • Выражение L-значения : выражение, имеющее L-значение;
  • Выражение R-значения : выражение, не имеющее L-значения.

В 2010 году Бьярн Страуструп дополнительно уточнил эти категории выражений в своей неофициальной записке комитета по стандартам C++ «Новая» терминология значений , чтобы принять во внимание семантику перемещения при разработке языка программирования C++:

  • Выражение L-значения : выражение, которое имеет L-значение и которое не всегда можно переместить (например,iвint j = i;, вC d = c;,c.m;, вreturn c;,cвthrow c;,fвf(););
  • Выражение значения X : выражение, которое имеет значение L и которое всегда можно переместить (например,C{}.mвstd::cout << C{}.m;);
  • Выражение PR-значения : выражение, которое не имеет L-значения и которое всегда можно переместить (например, in , in , in );
  • Выражение значения GL : выражение значения L или значения X;
  • Выражение R-значения : выражение PR-значения или X-значения.

В 2015 году Ричард Смит обновил категорию выражений PR-значений в своем документе комитета по стандартизации C++ « Гарантированное исключение копирования посредством упрощенных категорий значений», чтобы принять во внимание исключение обязательного перемещения при разработке языка программирования C++ (выражения PR-значений больше не перемещаются из):

  • Выражение PR-значения : выражение, которое не имеет L-значения и которое используется для инициализации (например, вint i = 1;,C{}вC c = C{};,1вi = 1;).
Другие вопросы по тегам