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


1356

У C ++ 03 вираз є або rvalue, або lvalue .

У C ++ 11 вираз може бути:

  1. оцінювати
  2. значення
  3. xvalue
  4. glvalue
  5. prvalue

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

  • Що це за нові категорії виразів?
  • Як ці нові категорії співвідносяться з існуючими категоріями rvalue та lvalue?
  • Чи відповідають категорії rvalue та lvalue у C ++ 0x такими, як вони є у C ++ 03?
  • Для чого потрібні ці нові категорії? Чи боги WG21 просто намагаються заплутати нас простих смертних?

9
@ Філіп Поттер: В C ++ 03? Так. Lvalue може використовуватися як rvalue, оскільки існує стандартне перетворення lvalue в rvalue.
Джеймс Мак-Нілліс

14
@Tyler: "Якщо ви можете призначити його, це value, інакше це rvalue." -> Неправильно, ви можете призначити клас rvalues: string("hello") = string("world").
fredoverflow

4
Зауважте, що це ціннісна категорія. Є більше властивостей, які можуть мати вирази. До них відносяться бітове поле (справжнє / хибне), тимчасове (справжнє / помилкове) та тип (тип його).
Йоханнес Шауб - ліб

30
Я думаю, що посилання на Фреда вище краще, ніж будь-яка відповідь тут. Хоча посилання мертва. Він був переміщений на: stroustrup.com/terminology.pdf
R. Martinho Fernandes

74
у C ++ навіть у ваших типів є типи
nielsbot

Відповіді:


634

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

Вся різанина почалася з рухомої семантики. Як тільки у нас є вирази, які можна перемістити та не скопіювати, раптом легко зрозуміти правила, які вимагають розрізнити вирази, які можна переміщувати, та в якому напрямку.

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

Вони потрібні? Можливо, ні, якщо ми хочемо втратити нові функції. Але для кращої оптимізації ми, мабуть, повинні їх охопити.

Цитуючи n3055 :

  • - Значення (так званий, історично, оскільки lvalues може з'явитися на лівій стороні вирази присвоювання) позначає функцію або об'єкт. [Приклад: Якщо Eце вираз типу вказівника, то *E це вираз lvalue, що посилається на об'єкт або функцію, на яку E вказує. Як інший приклад, результат виклику функції, типом повернення якої є посилання lvalue, є lvalue.]
  • Xvalue (е «закінчується» значення) також відноситься до об'єкту, як правило , ближче до кінця свого життя (так , щоб його ресурси можуть бути переміщені, наприклад). Значення xvalue є результатом певних видів виразів, що містять посилання rvalue. [Приклад: Результатом виклику функції, тип повернення якої є посилання rvalue, є xvalue.]
  • Glvalue ( «узагальнений» іменує) є іменують або xvalue .
  • Rvalue (так звана, історично, тому rvalues може з'явитися на правій стороні вирази присвоювання) є xvalue, тимчасовий об'єкт чи подоб'екти його, або значення , яке не пов'язане з об'єктом.
  • Prvalue ( «чистий» Rvalue) є Rvalue , що не є xvalue. [Приклад: Результат виклику функції, тип повернення якої не є посиланням, є первинним значенням]

Документ, про який йде мова, є чудовим посиланням на це питання, оскільки він показує точні зміни стандарту, які відбулися внаслідок впровадження нової номенклатури.


Дякую, ця відповідь дуже корисна! Але мій компілятор не згоден з вашими прикладами для xvalues ​​і prvalues; вони прямо протилежні. Повернення за посиланням rvalue дає мені первісне значення, а повернення за значенням дає мені xvalue. Ви їх змішали, або мій тестовий шар зламаний? Я спробував це з GCC 4.6.1, clang (від svn) та MSVC, і всі вони демонструють однакову поведінку.
Кім Грусман

На жаль, я просто перейшов за посиланням і помітив, що приклади є у джерелі. Я піду знайти свою копію стандарту і перевірте, що там написано ...
Кім Грусман

4
Я використовую макроси звідси для тестування різних виразів: stackoverflow.com/a/6114546/96963 Можливо, вони неправильно діагностують речі.
Кім Грусман

1
Додавання xvalue не для семантики ходу. Тільки з lvalue та rvalue семантика ходу, ідеальний посилання вперед та rvalue досі добре працюють. Я думаю, що xvalue призначений саме для оператора decltype: якщо вираз операнду xvalue, decltype дасть тип посилання rvalue.
ліганд

1
@MuhamedCicak "Кожен вираз - або значення, або значення": це правда; а стандарт (або документ n3055) не говорить про те, що він неправдивий. Причина, за якою це речення було закреслено, полягає в тому, що ви переглядали зміни між двома версіями документа. Вирок було знято, оскільки воно стало зайвим після додавання більш точного пояснення.
макс

337

Що це за нові категорії виразів?

FCD (n3092) має чудовий опис:

- Значення lvalue (так називалося історично, тому що lvalues ​​могли з'являтися з лівого боку виразу призначення) позначає функцію або об'єкт. [Приклад: Якщо E - вираз типу вказівника, то * E - це вираження значення, яке посилається на об'єкт або функцію, на яку вказує E. В якості іншого прикладу, результат виклику функції, типом повернення якої є посилання lvalue, є значення. —Закінчити приклад]

- xvalue (значення "eXpiring") також відноситься до об'єкта, як правило, наприкінці його терміну (наприклад, для переміщення його ресурсів). Значення xvalue є результатом певного виду виразів, що містять посилання rvalue (8.3.2). [Приклад: Результатом виклику функції, типом повернення якої є посилання rvalue, є xvalue. —Закінчити приклад]

- glvalue («узагальнений» lvalue) - це значення або xvalue.

- Rvalue (так називається історично, тому що rvalues ​​можуть з'являтися в правій частині виразів присвоєння) - це xvalue, тимчасовий об'єкт (12.2) або його суб'єкт, або значення, яке не пов'язане з об'єктом.

- Пріорител («чистий» оцінку) - це оцінку, яка не є xvalue. [Приклад: Результат виклику функції, тип повернення якої не є посиланням, є первинним значенням. Значення буквального типу, такого як 12, 7.3e5 або true, також є першим значенням. —Закінчити приклад]

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

Я пропоную вам прочитати весь розділ 3.10 Значення та рейтинги .

Як ці нові категорії співвідносяться з існуючими категоріями rvalue та lvalue?

Знову:

Таксономія

Чи відповідають категорії rvalue та lvalue у C ++ 0x такими, як вони є у C ++ 03?

Семантика рейтингів особливо розвинулася із впровадженням семантики руху.

Для чого потрібні ці нові категорії?

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


54
Мені тут подобається діаграма. Я думаю, що корисно почати відповідь із "Кожен вираз належить саме до однієї з основних класифікацій у цій систематиці: lvalue, xvalue або prvalue". Тоді легко використовувати діаграму, щоб показати, що три основні класи об'єднані для створення glvalue та rvalue.
Аарон Макдейд

2
"is glvalue" еквівалентно "is nije prvalue", а "is rvalue" еквівалентно "не lvalue".
Володимир Решетніков

2
Цей мені найбільше допоміг: bajamircea.github.io/assets/2016-04-07-move-forward/… (Діаграма Венна зі значеннями категорій)
Іван П

1
@AaronMcDaid Привіт, швидке запитання, якщо ви / хтось можете відповісти ... Чому б не називати glvalueяк lvalueі lvalueяк plvalue, щоб бути послідовним?
Віджай

184

Почну з вашого останнього запитання:

Для чого потрібні ці нові категорії?

Стандарт C ++ містить безліч правил, які стосуються категорії значення виразу. Деякі правила розрізняють lvalue та rvalue. Наприклад, якщо мова йде про дозвіл на перевантаження. Інші правила розрізняють glvalue та prvalue. Наприклад, у вас може бути glvalue з незавершеним або абстрактним типом, але немає попереднього значення з неповним або абстрактним типом. Перш ніж у нас була ця термінологія, правила, які насправді повинні розрізняти glvalue / prvalue, згадані lvalue / rvalue, і вони або ненавмисно помилялися, або містили багато пояснень та винятків із правила a la "... якщо тільки 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 - це значення:

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

165

Для чого потрібні ці нові категорії? Чи боги WG21 просто намагаються заплутати нас простих смертних?

Я не відчуваю, що інші відповіді (хороші, хоча їх багато) справді залучають відповідь на це конкретне питання. Так, ці категорії і такі існують, щоб дозволити рухати семантику, але складність існує з однієї причини. Це одне непорушне правило переміщення речей в C ++ 11:

Ти рухатимешся лише тоді, коли це безперечно безпечно робити.

Ось чому ці категорії існують: вміти говорити про цінності, де безпечно рухатися від них, і говорити про значення там, де їх немає.

У першій версії посилань на значення r переміщення відбувалося легко. Занадто легко. Досить просто, що було багато потенціалу для неявного переміщення речей, коли користувач насправді цього не мав на увазі.

Ось обставини, за яких безпечно щось пересувати:

  1. Коли це тимчасовий або його суб'єкт. (первісне значення)
  2. Коли користувач прямо заявив про переміщення .

Якщо ви це зробите:

SomeType &&Func() { ... }

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

Що це робить? У старих версіях специфікації, перш ніж увійти 5 значень, це спровокувало б крок. Звичайно, так і є. Ви передали посилання rvalue на конструктор, і таким чином воно прив'язується до конструктора, який приймає посилання rvalue. Це очевидно.

У цьому є лише одна проблема; ви не просили його перемістити. О, ви можете сказати, що це &&мало бути підказкою, але це не змінює факту, що воно порушило правило. valне є тимчасовим, оскільки часописи не мають імен. Можливо, ви продовжили термін служби тимчасового, але це означає, що це не тимчасово ; це як і будь-яка інша змінна стека.

Якщо це не тимчасово, і ви не просили його перемістити, то переміщення неправильне.

Очевидне рішення - зробити valцінність. Це означає, що ви не можете рухатися від неї. Добре, гаразд; вона названа, тому її значення.

Як тільки ви це зробите, ви більше не можете сказати, що це SomeType&&означає те саме, що ніде. Тепер ви зробили різницю між названими посиланнями на rvalue та неназваними посиланнями на rvalue. Ну, іменовані посилання rvalue - це значення; це було наше рішення вище. То що ми називаємо неназваними посиланнями на rvalue (повернене значення Funcзверху)?

Це не значення, тому що ви не можете перейти від значення. І нам потрібно вміти рухатися, повертаючи a &&; як інакше ви могли прямо сказати, щоб щось перенести? Це те, що std::moveповертається, зрештою. Це не ревальвація (старий стиль), тому що вона може бути з лівого боку рівняння (речі насправді дещо складніші. Дивіться це питання та коментарі нижче). Це не є ні значенням, ні оцінкою; це новий вид речі.

У нас є цінність, яку ви можете сприймати як значення, за винятком того, що з неї неявно можна перемістити . Ми називаємо це xvalue.

Зауважте, що xvalues ​​- це те, що змушує нас отримати інші дві категорії значень:

  • Prvalue - це справді нове ім'я попереднього типу rvalue, тобто вони є rvalues, які не є xvalues.

  • Glvalues ​​- це об'єднання xvalues ​​та lvalues ​​в одну групу, оскільки вони мають багато спільних властивостей.

Так що насправді, все зводиться до xvalues ​​та необхідності обмеження руху в точно та лише певних місцях. Ці місця визначаються категорією rvalue; prvalues ​​є неявними рухами, а xvalues ​​- явними рухами ( std::moveповертає значення xvalue).


11
@Thomas: Це приклад; не має значення, як він створює повернене значення. Важливо те, що він повертає a &&.
Нікол Болас

1
Примітка: prvalues ​​може бути також зліва від рівняння, як - як у X foo(); foo() = X;... З цієї основної причини я не можу повністю дотримуватися вищевказаної відмінної відповіді до кінця, тому що ви дійсно лише розрізняєте між новий xvalue та первинний стиль у старому стилі, заснований на тому, що він може бути на lhs.
Dan Nissenbaum

1
Xбути класом; X foo();будучи декларацією функції та foo() = X();рядком коду. (Я залишив другий набір дужок у foo() = X();своєму вище коментарі.) Щодо питання, яке я тільки що опублікував із цим використанням, див. Stackoverflow.com/questions/15482508/…
Dan Nissenbaum

1
@DanNissenbaum "xvalue не може бути зліва від виразу призначення" - чому б ні? Дивіться ideone.com/wyrxiT
Михайло

1
Освічуюча відповідь. Тут, безперечно, найкраща відповідь. Це дало мені обґрунтування введення нових категорій цінностей і того, що відбулося до цього.
Нікос

136

ІМХО, найкраще пояснення його значення дало нам Струструп + врахувати приклади Даніеля Сандора та Мохана :

Структура:

Тепер я серйозно переживав. Ясно, що ми рушили в глухий кут або безлад або те й інше. Я провів обід, роблячи аналіз, щоб побачити, які з властивостей (значень) були незалежними. Існували лише дві незалежні властивості:

  • 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.Зауважу, що вся ця дискусія / проблема виникає в результаті введення рецензійних посилань і переміщення семантики. Ці поняття просто не існують у світі Страчі, що складається з справедливих rvaluesі lvalues. Хтось зауважив, що ідеї, які

  • Кожен valueє lvalueабо anrvalue
  • Ан lvalueне є rvalueі rvalueне єlvalue

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

Це призвело до цілеспрямованої дискусії щодо називання. По-перше, нам потрібно було визначитися з « lvalue.Чи має lvalueозначати iMчи узагальнення» i? Під керівництвом Дуга Грегора ми перерахували місця у формулюванні основної мови, де це слово lvalueбуло кваліфіковане для того, щоб означати те чи інше. Список був зроблений і в більшості випадків і в найбільш складному / крихкому тексті в lvalueданий час засоби iM. Це класичне значення lvalue, оскільки "за старих часів" нічого не рухалося; move- це романне поняття в C++0x. Крім того, називання топлефтової точки W lvalueдає нам властивість, що кожне значення є lvalueабо rvalue, але не обидва.

Отже, верхня ліва точка Wє, lvalueа нижня права точка - rvalue.Що це робить нижній лівий і верхній правий точки? Ліва нижня точка - це узагальнення класичного рівня, що дозволяє рухатись. Так це 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-рейтингу.

Стів демонструє кінцевий продукт


14
так, краще прочитати оригінальні пропозиції та обговорення комітету С ++, ніж стандарт, якщо ви хочете зрозуміти, що вони мали на увазі: D
Іван Куш

8
Літерали не мають посвідчення і не можуть бути перенесені звідти; вони, тим не менш, корисні.
DrPizza

Я просто хочу щось уточнити. int && f () {повернути 1; } і MyClass && g () {повернути MyClass (); } повернути xvalue, правда? Тоді де я можу знайти тотожність виразів f (); і "g ();" У них є ідентичність, тому що є ще один вираз у зворотному висловлюванні, що стосується того самого об'єкта, що і вони посилаються - я розумію це правильно?
Даніель Сандор

6
@DrPizza Відповідно до стандарту: рядкові літерали є lvalues, усі інші літерали - prvalues. Строго кажучи, ви можете зробити аргумент, щоб сказати, що рядкові літерали повинні бути нерухомими, але це не так, як пишеться стандарт.
Брайан Ванденберг

59

ВСТУП

ISOC ++ 11 (офіційно ISO / IEC 14882: 2011) - це найновіша версія стандарту мови програмування C ++. Він містить деякі нові функції та поняття, наприклад:

  • rvalue посилання
  • xvalue, glvalue, категорії значень вираження prvalue
  • переміщення семантики

Якщо ми хотіли б зрозуміти поняття нових категорій значень виразів, ми повинні мати на увазі, що існують посилання rvalue та lvalue. Краще знати, що значення можуть бути передані посиланням, що не мають значення rvalue.

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

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

3.10 Значення та оцінки [basic.lval]

1 Вирази класифікуються відповідно до систематики на рисунку 1.

  • Значення lvalue (так називалося історично, тому що lvalues ​​могли з'являтися в лівій частині виразу присвоєння) позначає функцію або об'єкт. [Приклад: Якщо E - вираз типу вказівника, то * E - це вираження значення, яке посилається на об'єкт або функцію, на яку вказує E. В якості іншого прикладу, результат виклику функції, типом повернення якої є посилання lvalue, є значення. —Закінчити приклад]
  • Значення xvalue (значення "eXpiring") також відноситься до об'єкта, як правило, наприкінці його терміну (наприклад, для переміщення його ресурсів). Значення xvalue є результатом певного виду виразів, що містять посилання rvalue (8.3.2). [Приклад: Результатом виклику функції, типом повернення якої є посилання rvalue, є xvalue. —Закінчити приклад]
  • Glvalue («узагальнений» lvalue) - це значення або xvalue.
  • Rvalue (так називається історично, тому що rvalues ​​можуть з'являтися в правій частині виразу присвоєння) - це xvalue,
    тимчасовий об'єкт (12.2) або його суб'єкт, або значення, яке не
    пов'язане з об'єктом.
  • Першозначення ("чисте" значення) - це оцінку, яка не є xvalue. [Приклад: Результат виклику функції, тип повернення якої не є
    посиланням, є первинним значенням. Значення буквального типу, такого як 12, 7.3e5 або
    true, також є першим значенням. —Закінчити приклад]

Кожен вираз належить саме до однієї з основних класифікацій у цій систематиці: lvalue, xvalue або prvalue. Ця властивість виразу називається його ціннісною категорією.

Але я не зовсім впевнений, що цього підрозділу достатньо для чіткого розуміння понять, тому що "зазвичай" насправді не є загальним, "наприкінці терміну його життя" насправді не є конкретним, "з участю посилань на реальну оцінку" не зовсім зрозуміло, та "Приклад: Результатом виклику функції, тип повернення якої є посилання rvalue, є значення xva." звучить, як змія кусає хвіст.

ОСНОВНІ КАТЕГОРІЇ ЦІННОСТІ

Кожен вираз належить точно до однієї категорії первинних значень. Ці категорії значень - категорії lvalue, xvalue та prvalue.

значення

Вираз E належить до категорії lvalue тоді і лише тоді, коли E посилається на сутність, котра ВИНАГА мала ідентифікацію (адресу, ім'я чи псевдонім), що робить її доступною поза межами E.

#include <iostream>

int i=7;

const int& f(){
    return i;
}

int main()
{
    std::cout<<&"www"<<std::endl; // The expression "www" in this row is an lvalue expression, because string literals are arrays and every array has an address.  

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

- переклад на рецензію посилання на тип об'єкта, або

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

- вираз вказівника на члена, в якому перший операнд є xvalue, а другий операнд - вказівник на член даних.

Зауважте, що ефект з правил, наведених вище, полягає в тому, що названі посилання rvalue на об'єкти трактуються як lvalues, а неназвані посилання rvalue на об'єкти трактуються як xvalues; rvalue посилання на функції трактуються як значення як ім'я чи ні.

#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 належить до категорії первинного значення тоді і лише тоді, коли Е не належить ні до значення, ні до категорії 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.

оцінки

Вираз E належить до категорії rvalue тоді і лише тоді, коли E належить до категорії xvalue, або до категорії первинного значення.

Зауважте, що це визначення означає, що вираз E належить до категорії rvalue тоді і лише тоді, коли E посилається на сутність, яка не мала ідентичності, що робить її доступною поза E EET.

гливослови

Вираз E належить до категорії glvalue тоді і лише тоді, коли E належить до категорії lvalue або до категорії xvalue.

ПРАКТИЧНЕ ПРАВИЛА

Скотт Мейєр опублікував дуже корисне правило, щоб відрізнити оцінки від значення.

  • Якщо ви можете взяти адресу виразу, вираз є значенням.
  • Якщо тип виразу є посиланням на значення (наприклад, T & або const T & тощо), це вираження є значенням.
  • В іншому випадку вираз є оцінкою. Концептуально (а зазвичай і фактично) rvalues ​​відповідають тимчасовим об'єктам, таким як поверненим з функцій або створеному за допомогою неявного перетворення. Більшість буквальних значень (наприклад, 10 і 5.3) також є rvalues.

3
Усі приклади для lvalues ​​і всі приклади для xvalues ​​також є прикладами для glvalues. Дякую за редагування!
Даніель Сандор

1
Ти правий. Три категорії первинних значень є достатніми. Rvalue також не є необхідним. Я думаю, що rvalue та glvalue є стандартними для зручності.
Даніель Шандор

1
Якщо б важкий час , щоб зрозуміти змінна є prvalue. Я думав, що це має значення. До тих пір, поки стандарт 9.3.2 не говорить: У тілі нестатичної функції (9.3) член-слово це ключове слово. struct As{void f(){this;}}thisthis
r0ng

3
@ r0ng this- це перше значення, але *thisце значення
Xeverous

1
"www" не завжди однакова адреса. Це значення, тому що це масив .
wally

35

Категорії C ++ 03 занадто обмежені, щоб відображати введення посилань rvalue в атрибути вираження.

З їх введенням було сказано, що неназвана посилання rvalue оцінює значення rvalue, таким чином, щоб роздільна здатність перевантаження передавала перевагу прив'язкам rvalue, що змусило б вибирати конструктори переміщення над конструкторами копій. Але було встановлено, що це спричиняє проблеми навколо, наприклад, з динамічними типами та з кваліфікацією.

Щоб показати це, врахуйте

int const&& f();

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

На чернетках попереднього значення це було дозволено, оскільки в C ++ 03 рейтинги некласових типів ніколи не мають кваліфікації cv. Але передбачається , що constзастосовується в Rvalue-довідковому випадку, тому що тут ми дійсно відносяться до об'єктів (= пам'ять!), І опускаючи сопзЬ з Некласові rvalues в основному з тієї причини , що не існує жодного об'єкта навколо.

Питання для динамічних типів має подібний характер. У C ++ 03 rvalues ​​типу класу мають відомий динамічний тип - це статичний тип цього виразу. Тому що для того, щоб мати його іншим способом, вам потрібні посилання або відміни, які оцінюють як значення. Це не вірно з неназваними посиланнями на оцінку, але вони можуть демонструвати поліморфну ​​поведінку. Отже, щоб вирішити це,

  • неназвані посилання rvalue стають xvalues . Вони можуть бути кваліфікованими та потенційно відрізнятись їх динамічний тип Вони, як задумано, віддають перевагу посиланням rvalue під час перевантаження, і не пов'язуватимуться з посиланнями, що не мають значення lvalue.

  • Те, що раніше було оціночним значенням (літерали, об'єкти, створені за допомогою лиття до нереференційних типів), тепер стає первинним значенням . Вони мають такі ж переваги, як і xvalues ​​під час перевантаження.

  • Те, що раніше було значенням, залишається значенням.

І зроблено дві групи для того, щоб зафіксувати ті, які можуть бути кваліфіковані та можуть мати різні динамічні типи ( glvalues ), і ті, де перевантаження вважає за краще прив'язувати значення rvalue ( rvalues ).


1
відповідь, очевидно, розумна. xvalue - це просто rvalue, який може бути набраний резюме та динамічний набір!
ліганд

26

Я довго боровся з цим, поки не натрапив на пояснення cppreference.com про цінні категорії .

Насправді це досить просто, але я вважаю, що це часто пояснюється так, що важко запам’ятати. Тут це пояснено дуже схематично. Я цитую деякі частини сторінки:

Основні категорії

Первинні категорії значень відповідають двом властивостям виразів:

  • має ідентичність : можна визначити, чи вираз посилається на ту саму сутність як інший вираз, наприклад, порівнюючи адреси об'єктів або функції, які вони ідентифікують (отримані безпосередньо чи опосередковано);

  • може бути переміщено з : конструктор переміщення, оператор присвоєння переміщення або інша функція перевантаження, яка реалізує семантику переміщення, може прив'язуватися до виразу.

Вирази, які:

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

значення

Вираз lvalue ("ліве значення") - це вираз, який має ідентичність і з якого неможливо перемістити .

rvalue (до C ++ 11), prvalue (з C ++ 11)

Вираз "prvalue" ("чистий rvalue") - це вираз, який не має тотожності і з якого можна перемістити .

xvalue

Вираз xvalue ("закінчується значення") - це вираз, який має тотожність і з якого можна перемістити .

glvalue

Вираз glvalue ("узагальнене значення lvalue") - це вираз, який є або lvalue, або xvalue. Він має ідентичність . Він може або не може бути переміщений з.

rvalue (оскільки C ++ 11)

Вираз rvalue ("правильне значення") - це вираз, який є або первинним чи xvalue. Його можна перенести з . Він може мати або не мати особу.


1
У деяких книгах показано, що xvalues ​​мають свій x від "експерт" або "винятковий"
noɥʇʎԀʎzɐɹƆ

І ще важливіше - їх всебічний список прикладів.
Ciro Santilli 冠状 病毒 审查 六四 事件 法轮功

19

Як ці нові категорії співвідносяться з існуючими категоріями rvalue та lvalue?

Значення C ++ 03 l все ще є значенням C ++ 11, тоді як R + значення C ++ 03 називається первинним значенням в C ++ 11.


14

Одне доповнення до чудових відповідей вище, на пункт, який мене збентежив навіть після того, як я прочитав Stroustrup і подумав, що я зрозумів відмінність rvalue / lvalue. Коли бачиш

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 значенням. Не думайте про &&частину типу a; це просто щось, що говорить вам, що aдозволено пов'язувати.

Це особливо важливо для T&&аргументів типу в конструкторах. Якщо ти пишеш

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

ви скопіюєте _tв t. Тобі потрібно

Foo::Foo(T&& _t) : t{std::move(_t)} {}якщо ви хочете переїхати. Хіба що мій компілятор попередив мене, коли я не залишив move!


1
Я думаю, що цю відповідь можна було б уточнити. "Що aдозволено прив'язувати до": Звичайно, але у другому та другому рядках ваші змінні є c & b, і це не те, що пов'язує, і тип aтут не має значення, чи не так? Рядки були б однаковими, якби aбуло оголошено int a. Основна відмінність тут полягає в тому, що у рядку 1 а не повинно бути constприв’язане до 3.
Фелікс Домбек

12

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

Для деяких практичних експериментів із категоріями значень ви можете використовувати специфікатор 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 9.1. Clang 7.0.0 and msvc 19.16 are doing fine.
static_assert(IS_XVALUE(S().x)); 

Змішані категорії виглядають нудно, коли ви з'ясували основну категорію.

Для додаткових прикладів (та експериментів) перегляньте наступне посилання на провідник компілятора . Однак не заважайте читати збірку. Я додав багато компіляторів, щоб переконатися, що він працює у всіх поширених компіляторах.


Я думаю, що #define IS_GLVALUE(X) IS_LVALUE(X) || IS_XVALUE(X)насправді слід #define IS_GLVALUE(X) (IS_LVALUE(X) || IS_XVALUE(X))інакше подивитися на те, що станеться, якщо ви &&двоє IS_GLVALUE.
Габріель
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.