Was sind rWerte, lWerte, xWerte, glWerte und prWerte?


1356

In C ++ 03 ist ein Ausdruck entweder ein r-Wert oder ein l-Wert .

In C ++ 11 kann ein Ausdruck ein:

  1. rWert
  2. lWert
  3. xvalue
  4. glvalue
  5. Wert

Aus zwei Kategorien sind fünf Kategorien geworden.

  • Was sind diese neuen Kategorien von Ausdrücken?
  • In welcher Beziehung stehen diese neuen Kategorien zu den vorhandenen Kategorien rvalue und lvalue?
  • Sind die Kategorien rvalue und lvalue in C ++ 0x dieselben wie in C ++ 03?
  • Warum werden diese neuen Kategorien benötigt? Versuchen die WG21- Götter nur, uns Sterbliche zu verwirren?

9
@Philip Potter: In C ++ 03? Ja. Ein l-Wert kann als r-Wert verwendet werden, da eine Standardkonvertierung von l-Wert in r-Wert erfolgt.
James McNellis

14
@ Tyler: "Wenn Sie es zuweisen können, ist es ein Wert, andernfalls ist es ein Wert." -> Falsch, Sie können der Klasse rWerte zuweisen : string("hello") = string("world").
Fredoverflow

4
Beachten Sie, dass dies die Wertekategorie ist. Es gibt weitere Eigenschaften, die Ausdrücke haben können. Dazu gehören Bitfeld (wahr / falsch), temporär (wahr / falsch) und Typ (der Typ davon).
Johannes Schaub - litb

30
Ich denke, Freds Link oben ist besser als jede der Antworten hier. Der Link ist jedoch tot. Es wurde verschoben zu: stroustrup.com/terminology.pdf
R. Martinho Fernandes

74
In C ++ haben sogar Ihre Typen Typen
Nielsbot

Antworten:


634

Ich denke, dieses Dokument könnte als nicht ganz so kurze Einführung dienen: n3055

Das ganze Massaker begann mit der Bewegungssemantik. Sobald wir Ausdrücke haben, die verschoben und nicht kopiert werden können, erforderten plötzlich leicht verständliche Regeln die Unterscheidung zwischen Ausdrücken, die verschoben werden können und in welche Richtung.

Nach dem, was ich aufgrund des Entwurfs vermute, bleibt die Unterscheidung der R / L-Werte gleich, nur im Zusammenhang mit sich bewegenden Dingen wird es chaotisch.

Werden sie gebraucht? Wahrscheinlich nicht, wenn wir auf die neuen Funktionen verzichten möchten. Aber um eine bessere Optimierung zu ermöglichen, sollten wir sie wahrscheinlich annehmen.

Zitat n3055 :

  • Ein l - Wert (historisch so genannt, weil l-Werte auf der linken Seite eines Zuweisungsausdrucks erscheinen könnten) bezeichnet eine Funktion oder ein Objekt. [Beispiel: Wenn Ees sich um einen Ausdruck vom *E Zeigertyp handelt , handelt es sich um einen l-Wert-Ausdruck, der sich auf das Objekt oder die Funktion bezieht, auf die / die E verweist. Als weiteres Beispiel ist das Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp eine lWertreferenz ist, ein lWert.]
  • Ein x- Wert (ein "eXpiring" -Wert) bezieht sich auch auf ein Objekt, normalerweise gegen Ende seiner Lebensdauer (damit beispielsweise seine Ressourcen verschoben werden können). Ein x-Wert ist das Ergebnis bestimmter Arten von Ausdrücken, die r-Wert-Referenzen enthalten. [Beispiel: Das Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp eine rWertreferenz ist, ist ein xWert.]
  • Ein gl-Wert ("verallgemeinerter" l-Wert) ist ein l-Wert oder ein x-Wert .
  • Ein r - Wert (historisch so genannt, weil r-Werte auf der rechten Seite eines Zuweisungsausdrucks erscheinen könnten) ist ein x-Wert, ein temporäres Objekt oder ein Unterobjekt davon oder ein Wert, der keinem Objekt zugeordnet ist.
  • Ein Wert ("reiner" Wert) ist ein Wert, der kein x-Wert ist. [Beispiel: Das Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp keine Referenz ist, ist ein Wert]

Das fragliche Dokument ist eine gute Referenz für diese Frage, da es die genauen Änderungen des Standards zeigt, die infolge der Einführung der neuen Nomenklatur eingetreten sind.


Danke, diese Antwort ist wirklich hilfreich! Mein Compiler stimmt jedoch nicht mit Ihren Beispielen für xvalues ​​und prvalues ​​überein. Sie sind genau das Gegenteil. Die Rückgabe per rvalue-Referenz gibt mir einen pr-Wert, und die Rückgabe nach Wert gibt mir einen x-Wert. Hast du sie durcheinander gebracht oder ist mein Prüfstand kaputt? Ich habe dies mit GCC 4.6.1, clang (von svn) und MSVC versucht, und alle zeigen das gleiche Verhalten.
Kim Gräsman

Hoppla, ich bin gerade dem Link gefolgt und habe festgestellt, dass die Beispiele in der Quelle enthalten sind. Ich werde meine Kopie des Standards suchen und überprüfen, was darin steht ...
Kim Gräsman

4
Ich verwende die Makros von hier, um verschiedene Ausdrücke zu testen: stackoverflow.com/a/6114546/96963 Es kann sein, dass sie Dinge falsch diagnostizieren.
Kim Gräsman

1
Das Hinzufügen des x-Werts gilt nicht für die Verschiebungssemantik. Nur mit lvalue und rvalue funktionieren die Bewegungssemantik, die perfekte Vorwärts- und rvalue-Referenz immer noch gut. Ich denke, der xvalue ist nur für den decltype-Operator: Wenn der Operandenausdruck xvalue ist, gibt der decltype den Typ der rvalue-Referenz an.
Ligand

1
@ MuhamedCicak "Jeder Ausdruck ist entweder ein l-Wert oder ein r-Wert": es ist wahr; und der Standard (oder das Dokument n3055) sagt nicht, dass es falsch ist. Der Grund, warum dieser Satz durchgestrichen wurde, ist, dass Sie Änderungen zwischen zwei Versionen des Dokuments untersucht haben. Der Satz wurde entfernt, weil er überflüssig wurde, nachdem eine genauere Erklärung hinzugefügt wurde.
Max

337

Was sind diese neuen Kategorien von Ausdrücken?

Der FCD (n3092) hat eine hervorragende Beschreibung:

- Ein l-Wert (historisch so genannt, weil l-Werte auf der linken Seite eines Zuweisungsausdrucks erscheinen könnten) bezeichnet eine Funktion oder ein Objekt. [Beispiel: Wenn E ein Ausdruck vom Zeigertyp ist, dann ist * E ein l-Wert-Ausdruck, der sich auf das Objekt oder die Funktion bezieht, auf die E zeigt. Als weiteres Beispiel ist das Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp eine lWertreferenz ist, ein lWert. - Beispiel beenden]

- Ein x-Wert (ein "eXpiring" -Wert) bezieht sich auch auf ein Objekt, normalerweise gegen Ende seiner Lebensdauer (damit beispielsweise seine Ressourcen verschoben werden können). Ein x-Wert ist das Ergebnis bestimmter Arten von Ausdrücken mit r-Wert-Referenzen (8.3.2). [Beispiel: Das Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp eine rWertreferenz ist, ist ein xWert. - Beispiel beenden]

- Ein gl-Wert ("verallgemeinerter" l-Wert) ist ein l-Wert oder ein x-Wert.

- Ein r-Wert (historisch so genannt, weil r-Werte auf der rechten Seite eines Zuweisungsausdrucks erscheinen könnten) ist ein x-Wert, ein temporäres Objekt (12.2) oder ein Unterobjekt davon oder ein Wert, der keinem Objekt zugeordnet ist.

- Ein Wert ("reiner" Wert) ist ein Wert, der kein x-Wert ist. [Beispiel: Das Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp keine Referenz ist, ist ein Wert. Der Wert eines Literals wie 12, 7.3e5 oder true ist ebenfalls ein Wert. - Beispiel beenden]

Jeder Ausdruck gehört genau zu einer der grundlegenden Klassifikationen in dieser Taxonomie: lvalue, xvalue oder prvalue. Diese Eigenschaft eines Ausdrucks wird als Wertkategorie bezeichnet. [Hinweis: Die Erörterung jedes integrierten Operators in Abschnitt 5 gibt die Kategorie des Wertes an, den er liefert, und die Wertkategorien der Operanden, die er erwartet. Beispielsweise erwarten die integrierten Zuweisungsoperatoren, dass der linke Operand ein l-Wert und der rechte Operand ein pr-Wert ist, und ergeben als Ergebnis einen l-Wert. Benutzerdefinierte Operatoren sind Funktionen, und die Kategorien von Werten, die sie erwarten und ergeben, werden durch ihre Parameter- und Rückgabetypen bestimmt. - Endnote

Ich schlage vor, Sie lesen den gesamten Abschnitt 3.10 L- und r-Werte .

In welcher Beziehung stehen diese neuen Kategorien zu den vorhandenen Kategorien rvalue und lvalue?

Nochmal:

Taxonomie

Sind die Kategorien rvalue und lvalue in C ++ 0x dieselben wie in C ++ 03?

Die Semantik von Werten hat sich insbesondere mit der Einführung der Bewegungssemantik weiterentwickelt.

Warum werden diese neuen Kategorien benötigt?

Damit konnte die Bewegungskonstruktion / -zuordnung definiert und unterstützt werden.


54
Ich mag das Diagramm hier. Ich denke, es könnte nützlich sein, die Antwort mit "Jeder Ausdruck gehört genau zu einer der grundlegenden Klassifikationen in dieser Taxonomie: lvalue, xvalue oder prvalue" zu beginnen. Dann ist es einfach, das Diagramm zu verwenden, um zu zeigen, dass diese drei Grundklassen kombiniert werden, um glvalue und rvalue zu bilden.
Aaron McDaid

2
"is glvalue" entspricht "is not prvalue" und "is rvalue" entspricht "is not lvalue".
Vladimir Reshetnikov

2
Dieser hat mir am meisten geholfen: bajamircea.github.io/assets/2016-04-07-move-forward/… (Venn-Diagramm der Wertkategorien)
John P

1
@AaronMcDaid Hallo, kurze Frage, ob Sie / jemand antworten kann ... Warum nicht glvalueals lvalueund lvalueals benennen plvalue, um konsistent zu sein?
Vijay Chavda

184

Ich beginne mit Ihrer letzten Frage:

Warum werden diese neuen Kategorien benötigt?

Der C ++ - Standard enthält viele Regeln, die sich mit der Wertekategorie eines Ausdrucks befassen. Einige Regeln unterscheiden zwischen lvalue und rvalue. Zum Beispiel, wenn es um Überlastungsauflösung geht. Andere Regeln unterscheiden zwischen glvalue und prvalue. Beispielsweise können Sie einen Wert mit einem unvollständigen oder abstrakten Typ haben, aber es gibt keinen Wert mit einem unvollständigen oder abstrakten Typ. Bevor wir diese Terminologie hatten, bezogen sich die Regeln, die tatsächlich zwischen glvalue / prvalue unterscheiden müssen, auf lvalue / rvalue und sie waren entweder unbeabsichtigt falsch oder enthielten viele Erklärungen und Ausnahmen von der Regel a la "... es sei denn, der rvalue ist unbenannt Wertreferenz ... ". Es scheint also eine gute Idee zu sein, den Konzepten von glvalues ​​und prvalues ​​nur ihren eigenen Namen zu geben.

Was sind diese neuen Kategorien von Ausdrücken? In welcher Beziehung stehen diese neuen Kategorien zu den vorhandenen Kategorien rvalue und lvalue?

Wir haben immer noch die Begriffe lvalue und rvalue, die mit C ++ 98 kompatibel sind. Wir haben gerade die r-Werte in zwei Untergruppen unterteilt, x-Werte und pr-Werte, und wir bezeichnen l-Werte und x-Werte als gl-Werte. X-Werte sind eine neue Art von Wertekategorie für unbenannte r-Wert-Referenzen. Jeder Ausdruck ist einer dieser drei: lWert, xWert, prWert. Ein Venn-Diagramm würde folgendermaßen aussehen:

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

Beispiele mit Funktionen:

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

Vergessen Sie aber auch nicht, dass benannte rvalue-Referenzen lvalues ​​sind:

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

165

Warum werden diese neuen Kategorien benötigt? Versuchen die WG21-Götter nur, uns Sterbliche zu verwirren?

Ich glaube nicht, dass die anderen Antworten (obwohl viele von ihnen gut sind) wirklich die Antwort auf diese spezielle Frage erfassen. Ja, diese Kategorien und solche existieren, um die Bewegungssemantik zu ermöglichen, aber die Komplexität besteht aus einem Grund. Dies ist die einzige unantastbare Regel zum Verschieben von Inhalten in C ++ 11:

Du sollst dich nur bewegen, wenn es zweifellos sicher ist, dies zu tun.

Deshalb gibt es diese Kategorien: um über Werte sprechen zu können, bei denen es sicher ist, sich von ihnen zu entfernen, und um über Werte zu sprechen, bei denen dies nicht der Fall ist.

In der frühesten Version von R-Wert-Referenzen war die Bewegung leicht möglich. Zu leicht. Leicht genug, dass es viel Potenzial gab, Dinge implizit zu bewegen, wenn der Benutzer es nicht wirklich wollte.

Hier sind die Umstände, unter denen es sicher ist, etwas zu bewegen:

  1. Wenn es sich um ein temporäres Objekt oder ein Unterobjekt handelt. (Wert)
  2. Wenn der Benutzer ausdrücklich gesagt hat, dass er es verschieben soll .

Wenn du das tust:

SomeType &&Func() { ... }

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

Was macht das? In älteren Versionen der Spezifikation würde dies eine Bewegung provozieren, bevor die 5 Werte eingingen. Natürlich tut es das. Sie haben eine rvalue-Referenz an den Konstruktor übergeben, und daher wird diese an den Konstruktor gebunden, der eine rvalue-Referenz verwendet. Das ist offensichtlich.

Es gibt nur ein Problem damit; Sie haben nicht darum gebeten, es zu bewegen. Oh, man könnte sagen, das &&hätte ein Hinweis sein sollen, aber das ändert nichts an der Tatsache, dass es gegen die Regel verstoßen hat. valist keine temporäre, weil temporäre keine Namen haben. Möglicherweise haben Sie die Lebensdauer des temporären Objekts verlängert, dies bedeutet jedoch, dass es nicht vorübergehend ist . Es ist wie jede andere Stapelvariable.

Wenn es sich nicht um eine vorübergehende Bewegung handelt und Sie nicht darum gebeten haben, sie zu verschieben, ist die Verschiebung falsch.

Die naheliegende Lösung besteht darin, valeinen Wert zu erstellen. Dies bedeutet, dass Sie sich nicht davon entfernen können. OK, gut; Es heißt, also ist es ein Wert.

Sobald Sie das tun, können Sie nicht mehr sagen, dass SomeType&&dies überall dasselbe bedeutet. Sie haben jetzt zwischen benannten rWertreferenzen und unbenannten rWertreferenzen unterschieden. Nun, benannte rWertreferenzen sind lWerte; Das war unsere obige Lösung. Wie nennen wir also unbenannte rWert-Referenzen (den Rückgabewert von Funcoben)?

Es ist kein Wert, weil Sie sich nicht von einem Wert entfernen können. Und wir müssen in der Lage sein, uns zu bewegen, indem wir a zurückgeben &&; Wie sonst könnten Sie explizit sagen, etwas zu bewegen? Das ist es, was schließlich std::movezurückkehrt. Es ist kein Wert (im alten Stil), da er sich auf der linken Seite einer Gleichung befinden kann (die Dinge sind tatsächlich etwas komplizierter, siehe diese Frage und die Kommentare unten). Es ist weder ein Wert noch ein Wert; Es ist eine neue Art von Sache.

Was wir haben, ist ein Wert, den Sie als Wert behandeln können, außer dass er implizit von verschoben werden kann . Wir nennen es einen x-Wert.

Beachten Sie, dass wir durch x-Werte die beiden anderen Wertekategorien erhalten:

  • Ein prvalue ist eigentlich nur der neue Name für den vorherigen rvalue-Typ, dh es handelt sich um die rvalues, die keine xvalues ​​sind.

  • Gl-Werte sind die Vereinigung von x-Werten und l-Werten in einer Gruppe, da sie viele Eigenschaften gemeinsam haben.

Es kommt also wirklich auf x-Werte und die Notwendigkeit an, die Bewegung auf genau und nur bestimmte Orte zu beschränken. Diese Orte werden durch die Kategorie rvalue definiert. prvalues ​​sind die impliziten Bewegungen und xvalues ​​sind die expliziten Bewegungen ( std::movegibt einen xvalue zurück).


11
@ Thomas: Es ist ein Beispiel; Es spielt keine Rolle, wie der Rückgabewert erstellt wird. Was zählt ist, dass es a zurückgibt &&.
Nicol Bolas

1
Hinweis: Werte können auch auf der linken Seite einer Gleichung stehen - wie in X foo(); foo() = X;... Aus diesem Grund kann ich der obigen hervorragenden Antwort nicht bis zum Ende folgen, da Sie wirklich nur zwischen unterscheiden der neue xvalue und der prvalue im alten Stil, basierend auf der Tatsache, dass es auf dem lhs sein kann.
Dan Nissenbaum

1
Xeine Klasse sein; X foo();eine Funktionsdeklaration und foo() = X();eine Codezeile sein. (Ich habe den zweiten Satz von Klammern in foo() = X();meinem obigen Kommentar weggelassen
Dan Nissenbaum

1
@DanNissenbaum "xvalue kann nicht auf der linken Seite des Zuweisungsausdrucks stehen" - warum nicht? Siehe ideone.com/wyrxiT
Mikhail

1
Erleuchtende Antwort. Dies ist zweifellos die beste Antwort hier. Es gab mir Gründe für die Einführung der neuen Wertekategorien und was vorher passiert ist.
Nikos

136

IMHO, die beste Erklärung für seine Bedeutung gab uns Stroustrup + berücksichtigen Beispiele von Dániel Sándor und Mohan :

Stroustrup:

Jetzt war ich ernsthaft besorgt. Offensichtlich waren wir auf dem Weg in eine Sackgasse oder ein Chaos oder beides. Ich verbrachte die Mittagszeit damit, eine Analyse durchzuführen, um festzustellen, welche der Eigenschaften (der Werte) unabhängig waren. Es gab nur zwei unabhängige Eigenschaften:

  • has identity - dh und Adresse, ein Zeiger, der Benutzer kann bestimmen, ob zwei Kopien identisch sind, etc.
  • can be moved from - dh wir dürfen die Quelle einer "Kopie" in einem unbestimmten, aber gültigen Zustand verlassen

Dies führte mich zu dem Schluss, dass es genau drei Arten von Werten gibt (unter Verwendung des Regex-Notationstricks, einen Großbuchstaben zu verwenden, um ein Negativ anzuzeigen - ich hatte es eilig):

  • iM: hat Identität und kann nicht verschoben werden
  • im: hat Identität und kann verschoben werden von (z. B. das Ergebnis des Umsetzens eines l-Werts in eine r-Wert-Referenz)
  • Im: hat keine Identität und kann von verschoben werden.

    Die vierte Möglichkeit IM(hat keine Identität und kann nicht verschoben werden) ist C++in keiner anderen Sprache nützlich (oder, glaube ich).

Zusätzlich zu diesen drei grundlegenden Klassifikationen von Werten haben wir zwei offensichtliche Verallgemeinerungen, die den beiden unabhängigen Eigenschaften entsprechen:

  • i: hat Identität
  • m: kann verschoben werden von

Dies veranlasste mich, dieses Diagramm an die Tafel zu bringen: Geben Sie hier die Bildbeschreibung ein

Benennung

Ich stellte fest, dass wir nur begrenzte Namensfreiheit hatten: Die beiden Punkte links (beschriftet iMund i) sind das, was Menschen mit mehr oder weniger Formalität genannt haben, lvaluesund die beiden Punkte rechts (beschriftet mund Im) sind das, was Menschen mit mehr oder weniger Formalität sind angerufen haben rvalues. Dies muss sich in unserer Benennung widerspiegeln. Das heißt, das linke "Bein" des Wsollte Namen haben, die sich auf beziehen, lvalueund das rechte "Bein" des Wsollte Namen haben, die sich auf beziehen. rvalue.Ich stelle fest, dass diese ganze Diskussion / dieses Problem aus der Einführung von r-Wert-Referenzen und der Bewegungssemantik resultiert. Diese Begriffe existieren in Stracheys Welt, die aus gerecht rvaluesund besteht, einfach nicht lvalues. Jemand bemerkte, dass die Ideen das

  • Jeder valueist entweder ein lvalueoder einrvalue
  • Ein lvalueist kein rvalueund ein rvalueist keinlvalue

sind tief in unser Bewusstsein eingebettet, sehr nützliche Eigenschaften, und Spuren dieser Dichotomie finden sich überall im Standardentwurf. Wir waren uns alle einig, dass wir diese Eigenschaften erhalten (und präzisieren) sollten. Dies hat unsere Namensauswahl weiter eingeschränkt. Ich beobachtete , dass die Standard - Bibliothek Formulierung Anwendungen rvaluebedeuten m(die Verallgemeinerung), so dass die Erwartung und den Text der Standardbibliothek des rechte untere Punkt der bewahren Wsoll benannt werden rvalue.

Dies führte zu einer gezielten Diskussion der Namensgebung. Zuerst mussten wir uns für lvalue.Sollte lvaluebedeuten iModer die Verallgemeinerung entscheiden i? Unter der Leitung von Doug Gregor haben wir die Stellen in der Kernsprache aufgelistet, an denen das Wort lvalueals das eine oder andere bezeichnet wurde. Eine Liste wurde erstellt und bedeutet in den meisten Fällen und im schwierigsten / sprödesten Text lvaluederzeit iM. Dies ist die klassische Bedeutung von lvalue, weil "in den alten Tagen" nichts bewegt wurde; moveist ein neuartiger Begriff in C++0x. Wenn Sie den oberen linken Punkt von benennen W lvalue, erhalten Sie die Eigenschaft, dass jeder Wert ein lvalueoder ein ist rvalue, aber nicht beides.

Also, der obere linke Punkt des Wis lvalueund der untere rechte Punkt ist rvalue.Was macht das den unteren linken und oberen rechten Punkt aus? Der untere linke Punkt ist eine Verallgemeinerung des klassischen Wertes, die eine Bewegung ermöglicht. Es ist also ein generalized lvalue.Wir haben es genannt. glvalue.Sie können über die Abkürzung streiten, aber (glaube ich) nicht mit der Logik. Wir gingen davon aus, dass bei ernsthafter Verwendung generalized lvalue sowieso abgekürzt werden würde, also sollten wir es sofort tun (oder Verwirrung stiften). Der obere rechte Punkt des W ist weniger allgemein als der untere rechte (jetzt wie immer genannt rvalue). Dieser Punkt stellt den ursprünglichen reinen Begriff eines Objekts dar, von dem aus Sie sich bewegen können, da er nicht erneut referenziert werden kann (außer von einem Destruktor). Ich mochte den Satz specialized rvalueim Gegensatz zu generalized lvalueaberpure rvalueabgekürzt, um zu prvaluegewinnen (und wahrscheinlich zu Recht). Das linke Bein des W ist also lvalueund glvaluedas rechte Bein ist prvalueund rvalue.Übrigens ist jeder Wert entweder ein Gl-Wert oder ein Pr-Wert, aber nicht beides.

Dies lässt die obere Mitte des W: im; Das heißt, Werte, die Identität haben und verschoben werden können. Wir haben wirklich nichts, was uns zu einem guten Namen für diese esoterischen Bestien führt. Sie sind wichtig für Personen, die mit dem (Entwurfs-) Standardtext arbeiten, aber es ist unwahrscheinlich, dass sie zu einem bekannten Namen werden. Wir haben keine wirklichen Einschränkungen bei der Benennung gefunden, die uns leiten würden, also haben wir 'x' für das Zentrum, das Unbekannte, das Seltsame, das xpert nur oder sogar x-bewertet ausgewählt.

Steve zeigt das Endprodukt


14
Ja, es ist besser, Originalvorschläge und Diskussionen des C ++ - Komitees zu lesen als den Standard, wenn Sie verstehen wollen, was sie bedeuten: D
Ivan Kush

8
Literale haben keine Identität und können nicht entfernt werden. Sie sind trotzdem nützlich.
DrPizza

Ich möchte nur etwas klarstellen. int && f () {return 1; } und MyClass && g () {return MyClass (); } xvalue zurückgeben, richtig? Wo finde ich dann die Identität der Ausdrücke f (); und G();"? Sie haben Identität, weil die return-Anweisung einen anderen Ausdruck enthält, der sich auf dasselbe Objekt bezieht, auf das sie sich beziehen - verstehe ich das richtig?
Dániel Sándor

6
@DrPizza Gemäß dem Standard: String-Literale sind lvalues, alle anderen Literale sind prvalues. Genau genommen könnte man argumentieren, dass Nicht-String-Literale unbeweglich sein sollten, aber so wird der Standard nicht geschrieben.
Brian Vandenberg

59

EINFÜHRUNG

ISOC ++ 11 (offiziell ISO / IEC 14882: 2011) ist die neueste Version des Standards der Programmiersprache C ++. Es enthält einige neue Funktionen und Konzepte, zum Beispiel:

  • rWertreferenzen
  • xvalue, glvalue, prvalue Ausdruckswertkategorien
  • Semantik verschieben

Wenn wir die Konzepte der neuen Ausdruckswertkategorien verstehen möchten, müssen wir uns bewusst sein, dass es r- und l-Wert-Referenzen gibt. Es ist besser zu wissen, dass r-Werte an nicht konstante r-Wert-Referenzen übergeben werden können.

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

Wir können uns ein Bild von den Konzepten der Wertekategorien machen, wenn wir den Unterabschnitt mit dem Titel Lvalues ​​und rvalues ​​aus dem Arbeitsentwurf N3337 (dem Entwurf, der dem veröffentlichten ISOC ++ 11-Standard am ähnlichsten ist) zitieren.

3.10 L-Werte und r-Werte [basic.lval]

1 Ausdrücke werden gemäß der Taxonomie in Abbildung 1 kategorisiert.

  • Ein l-Wert (historisch so genannt, weil l-Werte auf der linken Seite eines Zuweisungsausdrucks erscheinen könnten) bezeichnet eine Funktion oder ein Objekt. [Beispiel: Wenn E ein Ausdruck vom Zeigertyp ist, dann ist * E ein l-Wert-Ausdruck, der sich auf das Objekt oder die Funktion bezieht, auf die E zeigt. Als weiteres Beispiel ist das Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp eine lWertreferenz ist, ein lWert. - Beispiel beenden]
  • Ein x-Wert (ein "eXpiring" -Wert) bezieht sich auch auf ein Objekt, normalerweise gegen Ende seiner Lebensdauer (damit beispielsweise seine Ressourcen verschoben werden können). Ein x-Wert ist das Ergebnis bestimmter Arten von Ausdrücken mit r-Wert-Referenzen (8.3.2). [Beispiel: Das Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp eine rWertreferenz ist, ist ein xWert. - Beispiel beenden]
  • Ein gl-Wert ("verallgemeinerter" l-Wert) ist ein l-Wert oder ein x-Wert.
  • Ein r-Wert (historisch so genannt, weil r-Werte auf der rechten Seite eines Zuweisungsausdrucks erscheinen könnten) ist ein x-Wert, ein
    temporäres Objekt (12.2) oder ein Unterobjekt davon oder ein Wert, der keinem
    Objekt zugeordnet ist.
  • Ein Wert ("reiner" Wert) ist ein Wert, der kein x-Wert ist. [Beispiel: Das Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp keine
    Referenz ist, ist ein Wert. Der Wert eines Literals wie 12, 7.3e5 oder
    true ist ebenfalls ein Wert. - Beispiel beenden]

Jeder Ausdruck gehört genau zu einer der grundlegenden Klassifikationen in dieser Taxonomie: lvalue, xvalue oder prvalue. Diese Eigenschaft eines Ausdrucks wird als Wertkategorie bezeichnet.

Ich bin mir jedoch nicht ganz sicher, ob dieser Unterabschnitt ausreicht, um die Konzepte klar zu verstehen, da "normalerweise" nicht wirklich allgemein ist, "gegen Ende seiner Lebensdauer" nicht wirklich konkret ist, "Einbeziehung von Wertreferenzen" nicht wirklich klar ist. und "Beispiel: Das Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp eine rWertreferenz ist, ist ein xWert." klingt wie eine Schlange ihren Schwanz beißt.

PRIMÄRWERTKATEGORIEN

Jeder Ausdruck gehört genau einer Primärwertkategorie an. Diese Wertekategorien sind die Kategorien lvalue, xvalue und prvalue.

lWerte

Der Ausdruck E gehört genau dann zur Kategorie lvalue, wenn sich E auf eine Entität bezieht, die BEREITS eine Identität (Adresse, Name oder Alias) hat, die sie außerhalb von E zugänglich macht.

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

xWerte

Der Ausdruck E gehört genau dann zur Kategorie xvalue, wenn dies der Fall ist

- das Ergebnis des impliziten oder expliziten Aufrufs einer Funktion, deren Rückgabetyp eine rWert-Referenz auf den Typ des zurückgegebenen Objekts ist, oder

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

- eine Umwandlung in einen r-Wert-Verweis auf den Objekttyp oder

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

- ein Zugriffsausdruck für Klassenmitglieder, der ein nicht statisches Datenelement vom Nichtreferenztyp angibt, in dem der Objektausdruck ein x-Wert ist, oder

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

- Ein Zeiger-zu-Element-Ausdruck, bei dem der erste Operand ein x-Wert und der zweite Operand ein Zeiger auf ein Datenelement ist.

Beachten Sie, dass die obigen Regeln bewirken, dass benannte rWert-Verweise auf Objekte als l-Werte und unbenannte rWert-Verweise auf Objekte als x-Werte behandelt werden. rWertreferenzen auf Funktionen werden als lWerte behandelt, unabhängig davon, ob sie benannt sind oder nicht.

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

Werte

Der Ausdruck E gehört genau dann zur Kategorie prvalue, wenn E weder zur Kategorie lvalue noch zur Kategorie xvalue gehört.

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 CATEGORIES

Es gibt zwei weitere wichtige Mischwertkategorien. Diese Wertekategorien sind rWert- und glWert-Kategorien.

rWerte

Der Ausdruck E gehört genau dann zur Kategorie rWert, wenn E zur Kategorie xWert oder zur Kategorie prWert gehört.

Beachten Sie, dass diese Definition bedeutet, dass der Ausdruck E genau dann zur r-Wert-Kategorie gehört, wenn sich E auf eine Entität bezieht, die keine Identität hat, die sie außerhalb von E YET zugänglich macht.

glWerte

Der Ausdruck E gehört genau dann zur Kategorie glvalue, wenn E zur Kategorie lvalue oder zur Kategorie xvalue gehört.

Eine praktische Regel

Scott Meyer hat veröffentlicht eine sehr nützliche Daumenregel rvalues von lvalues zu unterscheiden.

  • Wenn Sie die Adresse eines Ausdrucks übernehmen können, ist der Ausdruck ein Wert.
  • Wenn der Typ eines Ausdrucks eine l-Wert-Referenz ist (z. B. T & oder const T & usw.), ist dieser Ausdruck ein l-Wert.
  • Andernfalls ist der Ausdruck ein r-Wert. Konzeptionell (und typischerweise auch tatsächlich) entsprechen r-Werte temporären Objekten, z. B. solchen, die von Funktionen zurückgegeben oder durch implizite Typkonvertierungen erstellt wurden. Die meisten Literalwerte (z. B. 10 und 5.3) sind ebenfalls r-Werte.

3
Alle Beispiele für l-Werte und alle Beispiele für x-Werte sind ebenfalls Beispiele für gl-Werte. Vielen Dank für die Bearbeitung!
Dániel Sándor

1
Du hast recht. Die drei primären Wertkategorien sind ausreichend. Rvalue ist ebenfalls nicht erforderlich. Ich denke, rvalue und glvalue sind der Einfachheit halber Standard.
Dániel Sándor

1
Es war schwer zu verstehen, dass struct As{void f(){this;}}die thisVariable ein Wert ist. Ich dachte, thissollte ein Wert sein. Bis der Standard 9.3.2 besagt: Im Hauptteil einer nicht statischen (9.3) Elementfunktion ist das Schlüsselwort this ein prvalue-Ausdruck.
r0ng

3
@ r0ng thisist ein Wert, aber *thisist ein Wert
Xeverous

1
"www" hat nicht immer die gleiche Adresse. Es ist ein Wert, weil es ein Array ist .
Wally

35

Die Kategorien von C ++ 03 sind zu eingeschränkt, um die Einführung von r-Wert-Referenzen in Ausdrucksattribute korrekt zu erfassen.

Mit ihrer Einführung wurde gesagt, dass eine unbenannte r-Wert-Referenz einen r-Wert ergibt, so dass eine Überlastungsauflösung r-Wert-Referenzbindungen bevorzugt, wodurch Verschiebungskonstruktoren gegenüber Kopierkonstruktoren ausgewählt würden. Es wurde jedoch festgestellt, dass dies überall Probleme verursacht, beispielsweise bei dynamischen Typen und bei Qualifikationen.

Um dies zu zeigen, überlegen Sie

int const&& f();

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

Bei Entwürfen vor xvalue war dies zulässig, da in C ++ 03 r-Werte von Nicht-Klassentypen niemals cv-qualifiziert sind. Es ist jedoch beabsichtigt, dass dies constim Fall rvalue-reference gilt, da wir uns hier auf Objekte (= Speicher!) Beziehen und das Löschen von const aus r-Werten, die keine Klasse sind, hauptsächlich aus dem Grund erfolgt, dass kein Objekt vorhanden ist.

Das Problem für dynamische Typen ist ähnlicher Natur. In C ++ 03 haben rWerte vom Klassentyp einen bekannten dynamischen Typ - dies ist der statische Typ dieses Ausdrucks. Denn um es anders zu haben, benötigen Sie Referenzen oder Dereferenzen, die einen Wert ergeben. Dies gilt nicht für unbenannte r-Wert-Referenzen, sie können jedoch polymorphes Verhalten zeigen. Also, um es zu lösen,

  • Unbenannte rWertreferenzen werden zu xWerten . Sie können qualifiziert sein und möglicherweise einen anderen dynamischen Typ haben. Sie bevorzugen, wie beabsichtigt, rvalue-Referenzen während des Überladens und binden nicht an nicht konstante lvalue-Referenzen.

  • Was früher ein r-Wert war (Literale, Objekte, die durch Umwandlungen in Nichtreferenztypen erstellt wurden), wird jetzt zu einem pr-Wert . Sie haben die gleiche Präferenz wie x-Werte beim Überladen.

  • Was früher ein Wert war, bleibt ein Wert.

Und es werden zwei Gruppierungen durchgeführt, um diejenigen zu erfassen, die qualifiziert werden können und unterschiedliche dynamische Typen ( gl-Werte ) haben können, und solche, bei denen eine Überladung die r-Wert-Referenzbindung ( r-Werte ) bevorzugt .


1
Die Antwort ist offensichtlich vernünftig. xvalue ist nur ein rvalue, der lebenslaufqualifiziert und dynamisch typisiert werden kann!
Ligand

26

Ich habe mit diesem für eine lange Zeit zu kämpfen, bis ich auf der cppreference.com Erklärung der kam Wertkategorien .

Es ist eigentlich ziemlich einfach, aber ich finde, dass es oft auf eine Weise erklärt wird, die schwer zu merken ist. Hier wird es sehr schematisch erklärt. Ich werde einige Teile der Seite zitieren:

Hauptkategorien

Die primären Wertekategorien entsprechen zwei Eigenschaften von Ausdrücken:

  • hat Identität : Es ist möglich zu bestimmen, ob sich der Ausdruck auf dieselbe Entität wie ein anderer Ausdruck bezieht, z. B. durch Vergleichen der Adressen der Objekte oder der von ihnen identifizierten Funktionen (direkt oder indirekt erhalten).

  • kann verschoben werden von : Verschiebungskonstruktor, Verschiebungszuweisungsoperator oder einer anderen Funktionsüberladung, die Verschiebungssemantik implementiert, kann an den Ausdruck gebunden werden.

Ausdrücke, die:

  • Identität haben und nicht verschoben werden können als lWert-Ausdrücke bezeichnet werden ;
  • Identität haben und verschoben werden können als xvalue-Ausdrücke bezeichnet werden ;
  • keine Identität haben und verschoben werden können als prvalue-Ausdrücke bezeichnet werden ;
  • keine Identität haben und nicht verschoben werden können nicht verwendet werden.

lWert

Ein lvalue-Ausdruck ("linker Wert") ist ein Ausdruck, der Identität hat und von dem nicht verschoben werden kann .

rWert (bis C ++ 11), prWert (seit C ++ 11)

Ein prvalue-Ausdruck ("pure rvalue") ist ein Ausdruck, der keine Identität hat und von dem verschoben werden kann .

xvalue

Ein xvalue-Ausdruck ("expiring value") ist ein Ausdruck, der Identität hat und von dem verschoben werden kann .

glvalue

Ein glvalue-Ausdruck ("generalisierter lvalue") ist ein Ausdruck, der entweder ein lvalue oder ein xvalue ist. Es hat Identität . Es kann verschoben werden oder nicht.

rWert (seit C ++ 11)

Ein rvalue-Ausdruck ("rechter Wert") ist ein Ausdruck, der entweder ein prvalue- oder ein xvalue-Wert ist. Es kann von verschoben werden . Es kann Identität haben oder nicht.


1
In einigen Büchern wird gezeigt, dass x-Werte ihre x von "Experten" oder "Außergewöhnlichen" stammen
noɥʇʎԀʎzɐɹƆ

Und was noch wichtiger ist, ihre umfassende Beispielliste.
Ciro Santilli 法轮功 病毒 审查 六四 事件 20

19

In welcher Beziehung stehen diese neuen Kategorien zu den vorhandenen Kategorien rvalue und lvalue?

Ein C ++ 03-Wert ist immer noch ein C ++ 11-Wert, während ein C ++ 03-Wert in C ++ 11 als Wert bezeichnet wird.


14

Ein Nachtrag zu den oben genannten hervorragenden Antworten zu einem Punkt, der mich verwirrte, selbst nachdem ich Stroustrup gelesen und geglaubt hatte, die Unterscheidung zwischen Wert und Wert zu verstehen. Wenn du siehst

int&& a = 3,

Es ist sehr verlockend, das int&&als Typ zu lesen und daraus zu schließen, dass dies aein Wert ist. Es ist nicht:

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

ahat einen Namen und ist ipso facto ein Wert. Betrachten Sie das nicht &&als Teil der Art von a; Es sagt dir nur, woran aman sich binden darf.

Dies ist insbesondere für T&&Typargumente in Konstruktoren von Bedeutung. Wenn du schreibst

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

Sie kopieren _tin t. Du brauchst

Foo::Foo(T&& _t) : t{std::move(_t)} {}wenn du umziehen willst. Würde mich mein Compiler warnen, wenn ich das weglasse move?


1
Ich denke, diese Antwort könnte geklärt werden. "Was adarf gebunden werden ? ": Sicher, aber in Zeile 2 und 3 sind Ihre Variablen c & b, und es ist keine, an die gebunden wird, und die Art von aist hier irrelevant, nicht wahr? Die Zeilen wären die gleichen, wenn sie adeklariert würden int a. Der eigentliche Hauptunterschied besteht darin, dass in Zeile 1 a nicht constan 3
gebunden werden muss

12

Da die vorherigen Antworten die Theorie hinter den Wertekategorien ausführlich behandelten, möchte ich noch etwas hinzufügen: Sie können tatsächlich damit spielen und es testen.

Für einige praktische Experimente mit den Wertekategorien können Sie den Decltype-Bezeichner verwenden . Sein Verhalten unterscheidet explizit zwischen den drei primären Wertkategorien (xvalue, lvalue und prvalue).

Die Verwendung des Präprozessors erspart uns einige Eingaben ...

Hauptkategorien:

#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

Gemischte Kategorien:

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

Jetzt können wir (fast) alle Beispiele aus der Referenz zur Wertekategorie reproduzieren .

Hier einige Beispiele mit C ++ 17 (für terse 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)); 

Die gemischten Kategorien sind etwas langweilig, wenn Sie die primäre Kategorie herausgefunden haben.

Weitere Beispiele (und Experimente) finden Sie unter folgendem Link im Compiler-Explorer . Lesen Sie jedoch nicht die Versammlung. Ich habe viele Compiler hinzugefügt, um sicherzustellen, dass es auf allen gängigen Compilern funktioniert.


Ich denke #define IS_GLVALUE(X) IS_LVALUE(X) || IS_XVALUE(X)eigentlich sollte man sich #define IS_GLVALUE(X) (IS_LVALUE(X) || IS_XVALUE(X))sonst ansehen was passiert wenn ihr &&zwei seid IS_GLVALUE.
Gabriel Devillers
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.