Frage Was sind rvalues, lvalues, xvalues, glvalues ​​und prvalues?


In C ++ 03 ist ein Ausdruck entweder ein rvalue oder Wert.

In C ++ 11 kann ein Ausdruck ein

  1. rvalue
  2. Wert
  3. xwert
  4. glvalue
  5. Prvalue

Aus zwei Kategorien sind fünf Kategorien geworden.

  • Was sind diese neuen Kategorien von Ausdrücken?
  • Wie hängen diese neuen Kategorien mit den vorhandenen Kategorien rvalue und lvalue zusammen?
  • Sind die Kategorien rvalue und lvalue in C ++ 0x die gleichen wie in C ++ 03?
  • Warum werden diese neuen Kategorien benötigt? Sind die WG21 Götter, die nur versuchen, uns Normalsterbliche zu verwirren?

1104
2017-08-30 15:02


Ursprung


Antworten:


Ich denke, dieses Dokument könnte als eine nicht 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, verlangten plötzlich leicht zu erfassende Regeln die Unterscheidung zwischen zu bewegenden Ausdrücken und in welche Richtung.

Von dem, was ich basierend auf dem Entwurf vermute, bleibt der r / l-Wertunterschied derselbe, nur im Zusammenhang mit bewegten Dingen wird unordentlich.

Werden sie benötigt? Wahrscheinlich nicht, wenn wir die neuen Funktionen einbüßen wollen. Aber um eine bessere Optimierung zu ermöglichen, sollten wir sie wahrscheinlich annehmen.

Zitieren n3055:

  • Ein Wert (historisch so genannt, weil lvalues ​​auf dem erscheinen könnte linke Seite einer Aufgabe Ausdruck) bezeichnet eine Funktion oder ein Objekt. [Beispiel: If E ist ein Ausdruck des Zeigertyps dann *E ist ein lvalue-Ausdruck, auf den Bezug genommen wird das Objekt oder die Funktion, zu der E Punkte. Als ein anderes Beispiel, die Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp ist ein L-Wert-Verweis ist ein Wert.] 
  • Ein xwert (ein "EXpiring" -Wert bezieht sich auch auf ein Objekt, normalerweise nahe dem Ende seines Lebenszeit (so dass seine Ressourcen können bewegt werden, zum Beispiel). Ein xvalue ist das Ergebnis bestimmter Arten von Ausdrücke mit rvalue Verweise. [Beispiel: Die Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp ist ein Rvalue-Referenzwert ein xvalue.]
  • EIN glvalue   ("Verallgemeinerter" Wert) ist ein Wert oder xwert.
  • Ein rvalue (sogenannt, historisch, weil rvalues ​​könnte erscheinen auf der rechten Seite eines Zuweisungsausdruck) ist ein xvalue, ein temporäres Objekt oder Unterobjekt davon, oder ein Wert, der ist nicht mit einem Objekt verbunden.
  • EIN Prvalue ("Purer" rvalue) ist ein rvalue Das ist kein xvalue. [Beispiel: Die Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp ist keine Referenz ist a Prvalue]

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


530
2017-08-30 15:09



Was sind diese neuen Kategorien von Ausdrücken?

Das FCD (n3092) hat eine ausgezeichnete Beschreibung:

- Ein Wert (so genannt historisch, weil lvalues ​​auf dem erscheinen konnte   linke Seite einer Aufgabe   Ausdruck) bezeichnet eine Funktion oder   ein Objekt. [Beispiel: Wenn E ein ist   Ausdruck des Zeigertyps dann   * E ist ein lvalue-Ausdruck, der sich auf das Objekt oder die Funktion bezieht, auf die E   Punkte. Als ein weiteres Beispiel, das Ergebnis   eine Funktion aufrufen, deren Rückgabe   type ist eine lvalue-Referenz ist ein   Wert. -End-Beispiel]

- Ein xvalue (an   "EXpiring" -Wert bezieht sich auch auf ein   Objekt, normalerweise nahe dem Ende seines   Lebenszeit (so dass seine Ressourcen sein können   bewegt, zum Beispiel). Ein xvalue ist der   Ergebnis bestimmter Arten von Ausdrücken   mit rvalue Referenzen (8.3.2). [   Beispiel: Das Ergebnis des Aufrufs von a   Funktion, deren Rückgabetyp ein ist   rvalue Referenz ist ein xvalue. -Ende   Beispiel]

- Ein glvalue ("verallgemeinert")   lvalue) ist ein lvalue oder ein xvalue.

-   Ein rvalue (so genannt, historisch,   weil Rvalues ​​auf dem erscheinen könnten   rechte Seite einer Aufgabe   Ausdrücke) ist ein xvalue, ein temporäres   Objekt (12.2) oder Unterobjekt davon, oder   ein Wert, der nicht mit einem verknüpft ist   Objekt.

- Ein prvalue ("reiner" rvalue) ist   ein rvalue, der kein xvalue ist. [   Beispiel: Das Ergebnis des Aufrufs von a   Funktion, deren Rückgabetyp nicht a   Referenz ist ein Prvalue. Der Wert von a   wörtlich wie 12, 7.3e5 oder wahr ist   auch ein prvalue. -End-Beispiel]

Jeden   Ausdruck gehört genau zu einem von   die grundlegenden Klassifikationen in   Diese Taxonomie: lvalue, xvalue oder   Prvalue. Diese Eigenschaft eines   Ausdruck heißt sein Wert   Kategorie. [Anmerkung: Die Diskussion von   jeder eingebaute Operator in Abschnitt 5   zeigt die Kategorie des Wertes an   Erträge und die Wertkategorien der   Operanden erwartet es. Zum Beispiel, die   eingebaute Zuweisungsoperatoren erwarten   dass der linke Operand ein Lvalue ist und   dass der rechte Operand ein Prvalue ist   und ergibt als Ergebnis einen L-Wert.   Benutzerdefinierte Operatoren sind Funktionen,   und die Kategorien von Werten sie   Erwartung und Ertrag werden bestimmt durch   ihre Parameter und Rückgabetypen. -Ende   Hinweis

Ich schlage vor, Sie lesen den gesamten Abschnitt 3.10 Lvalues ​​und rvalues obwohl.

Wie hängen diese neuen Kategorien mit den vorhandenen Kategorien rvalue und lvalue zusammen?

Nochmal:

Taxonomy

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

Die Semantik von Rvalues ​​hat sich besonders mit der Einführung der Bewegungssemantik entwickelt.

Warum werden diese neuen Kategorien benötigt?

So konnte die Konstruktion / Zuordnung der Bewegung definiert und unterstützt werden.


303
2017-08-31 08:08



Ich fange mit deiner letzten Frage an:

Warum werden diese neuen Kategorien benötigt?

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

Was sind diese neuen Kategorien von Ausdrücken?   Wie hängen diese neuen Kategorien mit den vorhandenen Kategorien rvalue und lvalue zusammen?

Wir haben immer noch die Ausdrücke lvalue und rvalue, die mit C ++ 98 kompatibel sind. Wir haben die Rvalues ​​einfach in zwei Untergruppen aufgeteilt, xvalues ​​und prvalues, und beziehen uns auf lvalues ​​und xvalues ​​als glvalues. Xvalues ​​sind eine neue Art von Wertkategorie für unbenannte rvalue-Referenzen. Jeder Ausdruck ist einer dieser drei: lvalue, xvalue, prvalue. Ein Venn-Diagramm würde so aussehen:

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

Beispiele mit Funktionen:

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

Vergessen Sie aber nicht, dass benannte rvalue-Referenzen lvales sind:

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

156
2018-03-04 06:32



Warum werden diese neuen Kategorien benötigt? Versuchen die WG21-Götter nur, uns Normalsterbliche 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 Bewegungssemantik zu erlauben, aber die Komplexität existiert aus einem Grund. Dies ist die eine Unverletzlichkeitsregel, Dinge in C ++ 11 zu verschieben:

Du sollst dich nur bewegen, wenn es fraglos sicher ist.

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

In der frühesten Version der r-Wert-Referenzen erfolgte die Bewegung leicht. Auch leicht. Einfach 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 oder Unterobjekt handelt. (Prvalue)
  2. Wenn der Benutzer hat ausdrücklich gesagt, es zu bewegen.

Wenn du das tust:

SomeType &&Func() { ... }

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

Was macht das? In älteren Versionen der Spezifikation würde dies, bevor die 5 Werte eintrafen, eine Bewegung auslösen. Natürlich tut es das. Sie haben einen Rvalue-Verweis an den Konstruktor übergeben und damit an den Konstruktor gebunden, der eine Rvalue-Referenz verwendet. Das ist offensichtlich.

Es gibt nur ein Problem damit; Du hast es nicht getan Fragen um es zu bewegen. Oh, du könntest das sagen && sollte ein Hinweis gewesen sein, aber das ändert nichts an der Tatsache, dass es die Regel gebrochen hat. val ist kein temporäres, weil temporäre Namen keine Namen haben. Du magst die Lebensdauer des Temporären verlängert haben, aber das bedeutet, dass es nicht so ist vorübergehend; Es ist wie jede andere Stapelvariable.

Wenn es kein vorübergehendes ist, und Sie nicht gebeten haben, es zu bewegen, dann ist das Bewegen falsch.

Die offensichtliche Lösung ist zu machen val ein Wert. 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 das nicht mehr sagen SomeType&& bedeutet überall dasselbe. Sie haben nun zwischen benannten rvalue-Referenzen und unbenannten rvalue-Referenzen unterschieden. Nun, benannte Rvalue-Referenzen sind lvalues; das war unsere Lösung oben. Also, wie nennen wir unbenannte rvalue Referenzen (der Rückgabewert von Func über)?

Es ist kein Lvalue, da Sie sich nicht von einem Lvalue bewegen können. Und wir brauchen sich bewegen können, indem man a zurückgibt &&; wie sonst könnten Sie explizit sagen, etwas zu bewegen? Das ist was std::movekehrt zurück. Es ist kein rvalue (alter Stil), weil es auf der linken Seite einer Gleichung sein kann (Dinge sind tatsächlich ein bisschen komplizierter, siehe diese Frage und die Kommentare unten). Es ist weder ein L-Wert noch ein R-Wert; es ist eine neue Sache.

Was wir haben, ist ein Wert, den Sie als einen Wert behandeln können, außer dass es implizit bewegbar ist von. Wir nennen es einen xvalue.

Beachte, dass xvalues ​​uns dazu bringt, die anderen beiden Wertekategorien zu erreichen:

  • Ein Pr-Wert ist eigentlich nur der neue Name für den vorherigen Typ von R-Wert, d. H. Es sind die R-Werte, die sind nicht xvalues.

  • Glvalues ​​sind die Vereinigung von xvalues ​​und lvalues ​​in einer Gruppe, da sie viele Eigenschaften gemeinsam haben.

Es kommt also auf xvalues ​​und die Notwendigkeit an, die Bewegung auf genau bestimmte Orte zu beschränken. Diese Orte werden durch die Kategorie rvalue definiert; Prvalues ​​sind die impliziten Moves und xvalues ​​sind die expliziten Moves (std::move gibt einen xvalue zurück).


130
2017-07-03 12:30



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

Stroustrup:

Jetzt war ich ernsthaft besorgt. Offensichtlich waren wir auf eine Sackgasse oder   ein Durcheinander oder beides. Ich verbrachte das Mittagessen damit, eine Analyse zu machen, um zu sehen, welche   der Eigenschaften (der Werte) waren unabhängig. Es waren nur zwei   unabhängige Eigenschaften:

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

Dies führte mich zu der Schlussfolgerung, dass es genau drei Arten gibt   Werte (mit dem Regex-Notationstrick, einen Großbuchstaben zu verwenden   ein Negativ anzeigen - ich hatte es eilig):

  • iM: hat Identität und kann nicht verschoben werden
  • im: hat Identität und kann verschoben werden (z. B. das Ergebnis der Umwandlung eines lvalue in eine rvalue-Referenz)
  • Im: hat keine Identität und kann von der vierten Möglichkeit (IM: hat keine Identität und kann nicht bewegt werden) ist nicht   nützlich in C++ (oder, glaube ich) in einer anderen Sprache.

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

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

Dies brachte mich dazu, dieses Diagramm an die Tafel zu setzen:    enter image description here

Benennung

Ich bemerkte, dass wir nur begrenzte Freiheit hatten, zu benennen: Die zwei Punkte zu   die linke (beschriftet iM und i) sind was für Leute mit mehr oder weniger   Formalität genannt haben lvalues und die zwei Punkte auf der rechten Seite   (beschriftet m und Im) sind Menschen mit mehr oder weniger Formalität   habe angerufen rvalues. Dies muss sich in unserer Benennung widerspiegeln. Das ist,   das linke "Bein" der W sollte mit Namen in Verbindung stehen lvalue und das   rechtes "Bein" des W sollte mit Namen in Verbindung stehen rvalue. Ich erwähne   dass diese ganze Diskussion / Problem von der Einführung von entstehen   rvalue Referenzen und Verschieben Semantik. Diese Begriffe existieren einfach nicht   in Stracheys Welt bestehend aus nur rvalues und lvalues. Jemand   beobachtet, dass die Ideen, die

  • Jeden value ist entweder ein lvalue oder rvalue
  • Ein lvalue ist nicht ein rvalue und ein rvalue ist nicht ein lvalue 

sind tief in unser Bewusstsein eingebettet, sehr nützliche Eigenschaften, und   Spuren dieser Dichotomie finden sich überall im Normungsentwurf. Wir   Alle waren sich einig, dass wir diese Eigenschaften bewahren sollten (und sie machen sollten)   präzise). Dies hat unsere Benennungsoptionen weiter eingeschränkt. Das habe ich beobachtet   der Standard-Bibliothekswortlaut verwendet rvalue meinen m (das   Verallgemeinerung), so dass die Erwartung und der Text der   Standardbibliothek der rechte untere Punkt der W sollte benannt werden    rvalue.

Dies führte zu einer fokussierten Diskussion der Namensgebung. Zuerst mussten wir uns entscheiden   auf lvalue. Sollte lvalue bedeuten iM oder die Verallgemeinerung i? LED   von Doug Gregor haben wir die Orte in der Kernsprache aufgelistet   wo das Wort lvalue war qualifiziert, das eine oder andere zu meinen. EIN   Liste wurde gemacht und in den meisten Fällen und in der schwierigsten / spröde Text    lvalue bedeutet derzeit iM. Dies ist die klassische Bedeutung von Lvalue   weil "in den alten Zeiten" nichts bewegt wurde; move ist eine neue Idee   im C++0x. Den oberen Punkt des W  lvalue gibt uns   die Eigenschaft, dass jeder Wert ein ist lvalue oder rvalue, aber nicht beide.

Also, der obere linke Punkt der W ist lvalue und der untere rechte Punkt   ist rvalue. Was macht das unten links und oben rechts?   Der untere linke Punkt ist eine Verallgemeinerung des klassischen Wertes,   Bewegung zulassen. So ist es ein generalized lvalue. Wir haben es genannt    glvalue. Du kannst über die Abkürzung streiten, aber (denke ich) nicht   mit der Logik. Wir haben das ernst genommen generalized lvalue   würde sowieso irgendwie abgekürzt werden, also sollten wir es besser machen   sofort (oder Risiko Verwirrung). Der obere rechte Punkt des W ist weniger   allgemein als unten rechts (jetzt wie immer genannt rvalue). Das   Punkt repräsentieren die ursprüngliche reine Vorstellung von einem Objekt, das Sie bewegen können   aus, weil es nicht erneut referenziert werden kann (außer durch einen Destruktor).   Ich mochte den Ausdruck specialized rvalue im Kontrast zu generalized lvalue aber pure rvalue abgekürzt zu prvalue gewonnen (und   wahrscheinlich zu Recht). Also, das linke Bein des W ist lvalue und    glvalue und das rechte Bein ist prvalue und rvalue. Übrigens,   Jeder Wert ist entweder ein glvalue oder ein prvalue, aber nicht beides.

Dies lässt die obere Mitte der W: im; das heißt, Werte, die haben   Identität und kann bewegt werden. Wir haben wirklich nichts, was führt   uns zu einem guten Namen für diese esoterischen Bestien. Sie sind wichtig für   Menschen, die mit dem (Entwurfs-) Standardtext arbeiten, aber es unwahrscheinlich sind   werde ein bekannter Name. Wir haben keine wirklichen Einschränkungen für die gefunden   Benennung, um uns zu führen, so haben wir "x" für das Zentrum, das Unbekannte, das   seltsam, nur der xpert oder sogar x-rated.

Steve showing off the final product


92
2018-01-20 13:46



EINFÜHRUNG

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

  • rvalue Referenzen
  • xvalue, glvalue, prvalue Ausdruckswertkategorien
  • Semantik verschieben

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

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

Wenn wir den Unterabschnitt mit dem Titel Lvalues ​​and rvalues ​​aus dem Arbeitsentwurf N3337 (dem ähnlichsten Entwurf zu dem veröffentlichten ISOC ++ 11-Standard) zitieren, können wir eine Vorstellung von den Konzepten der Wertkategorien gewinnen.

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

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

  • Ein Lvalue (historisch so genannt, weil lvalues ​​auf der linken Seite eines Zuweisungsausdrucks erscheinen könnte) bezeichnet eine Funktion   oder ein Objekt. [Beispiel: Wenn E ein Ausdruck des Zeigertyps ist, dann   * E ist ein Lvalue-Ausdruck, der sich auf das Objekt oder die Funktion bezieht, auf die E zeigt. Als ein weiteres Beispiel das Ergebnis des Aufrufs einer Funktion   Wobei der Rückgabetyp eine Lvalue-Referenz ist, ist ein Lvalue. -End-Beispiel]
  • Ein xvalue (ein "eXpiring" -Wert) bezieht sich auch auf ein Objekt, normalerweise am Ende seiner Lebensdauer (so dass seine Ressourcen verschoben werden können, z   Beispiel). Ein xvalue ist das Ergebnis bestimmter Arten von Ausdrücken   mit rvalue Referenzen (8.3.2). [Beispiel: Das Ergebnis des Aufrufs   Eine Funktion, deren Rückgabetyp eine rvalue-Referenz ist, ist ein xvalue. -Ende   Beispiel]
  • Ein glvalue ("verallgemeinerter" lvalue) ist ein lvalue oder ein xvalue.
  • Ein rvalue (historisch gesehen, weil rvalues ​​auf der rechten Seite eines Zuweisungsausdrucks erscheinen könnte) ist ein xvalue, a
      temporäres Objekt (12.2) oder Unterobjekt davon oder ein Wert, der nicht
      einem Objekt zugeordnet.
  • Ein Pr-Wert ("reiner" R-Wert) ist ein R-Wert, der kein x-Wert ist. [Beispiel: Das Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp nicht a ist
      Referenz ist ein Prvalue. Der Wert eines Literals wie 12, 7.3e5 oder
      Wahr ist auch ein Prvalue. -End-Beispiel]

Jeder Ausdruck gehört zu genau einem der Grundlegenden   Klassifizierungen in dieser Taxonomie: lvalue, xvalue oder prvalue. Dies   Die Eigenschaft eines Ausdrucks wird als Wertkategorie bezeichnet.

Aber ich bin mir nicht ganz sicher, dass dieser Abschnitt ist genug, um die Konzepte klar zu verstehen, denn „in der Regel“ nicht wirklich allgemein ist „am Ende seiner Lebenszeit“ ist nicht wirklich konkret, „Einbeziehung rvalue Referenzen“ ist nicht wirklich klar, und "Beispiel: Das Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp eine rvalue-Referenz ist, ist ein xvalue." hört sich an wie eine Schlange, die ihren Schwanz beißt.

PRIMÄRWERTKATEGORIEN

Jeder Ausdruck gehört zu genau einer primären Wertkategorie. Diese Wertkategorien sind Lvalue-, Xvalue- und Prvalue-Kategorien.

Werte

Der Ausdruck E gehört genau dann zur lvalue-Kategorie, wenn E auf eine Entität verweist, für die ALREADY eine Identität (Adresse, Name oder Alias) hatte, die es außerhalb von E zugänglich macht.

#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

Der Ausdruck E gehört genau dann zur Kategorie xvalue

- das Ergebnis des Aufrufs einer Funktion, entweder implizit oder explizit, deren Rückgabetyp eine rvalue-Referenz auf den zurückgegebenen Objekttyp 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 rvalue-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 Klassenmitgliedszugriffsausdruck, der einen nicht statischen Datenmember des Nicht-Verweistyps bezeichnet, in dem der Objektausdruck ein xvalue oder ist

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, in dem der erste Operand ein x-Wert und der zweite Operand ein Zeiger auf das Datenelement ist.

Beachten Sie, dass die oben genannten Regeln bewirken, dass benannte rvalue-Verweise auf Objekte als lvalues ​​behandelt werden und unbenannte rvalue-Verweise auf Objekte als xvalues ​​behandelt werden. rvalue-Verweise auf Funktionen werden als lvalues ​​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;
}

Prvalues

Der Ausdruck E gehört genau dann zur Kategorie prvalue, wenn E weder zum lvalue noch zum xvalue-Kategorie 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;
}

GEMISCHTE WERTKATEGORIEN

Es gibt zwei weitere wichtige Mischwertkategorien. Diese Wertkategorien sind Rvalue- und Glvalue-Kategorien.

rvalues

Der Ausdruck E gehört genau dann zur Kategorie rvalue, wenn E zur Kategorie xvalue oder zur Kategorie prvalue gehört.

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

glvalues

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 Faustregel, um rvalues ​​von lvalues ​​zu unterscheiden.

  • Wenn Sie die Adresse eines Ausdrucks verwenden können, ist der Ausdruck ein Lvalue.
  • Wenn der Typ eines Ausdrucks eine L-Wert-Referenz ist (z. B. T & sub0; oder const T & sub1; usw.), ist dieser Ausdruck ein L-Wert.
  • Ansonsten ist der Ausdruck ein rvalue. Konzeptionell (und typischerweise auch tatsächlich) entsprechen R-Werte temporären Objekten, wie z   wie diejenigen, die von Funktionen zurückgegeben oder durch impliziten Typ erstellt wurden   Konvertierungen. Die meisten Literalwerte (z. B. 10 und 5,3) sind ebenfalls R-Werte.

34
2017-08-30 16:46



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

Mit der Einführung von ihnen, hieß es, dass ein ungenannter rvalue Verweis auf einen R-Wert auswertet, so dass die Überladungsauflösung rvalue Referenz Bindungen bevorzugen, die sie bewegen Bauer über Kopierkonstruktoren wählen würden. Es wurde jedoch festgestellt, dass dies überall Probleme verursacht, zum Beispiel mit Dynamische Typen und mit Qualifikationen.

Um dies zu zeigen, überlegen

int const&& f();

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

Bei Vor-Xvalue-Entwürfen war dies zulässig, da R-Werte von Nicht-Klassen-Typen in C ++ 03 nie cv-qualifiziert sind. Aber das ist beabsichtigt const gilt im rvalue-Referenzfall, weil wir hier machen beziehen sich auf Objekte (= Speicher!), und das Ablegen von Konstanten aus Nicht-Klassen-Rvalues ​​liegt hauptsächlich daran, dass es kein Objekt in der Umgebung gibt.

Das Problem für dynamische Typen ist ähnlich. In C ++ 03 haben Rvalues ​​des Klassentyps einen bekannten dynamischen Typ - es ist der statische Typ dieses Ausdrucks. Um es anders auszudrücken, benötigen Sie Referenzen oder Dereferenzen, die zu einem Lvalue ausgewertet werden. Das gilt nicht für unbenannte rvalue-Referenzen, aber sie können polymorphes Verhalten zeigen. Um es zu lösen,

  • unbenannte rvalue-Referenzen werden xvalues. Sie können qualifiziert sein und möglicherweise ihren dynamischen Typ unterscheiden. Sie bevorzugen, wie beabsichtigt, rvalue-Referenzen beim Überladen und binden nicht an nicht-konstante lvalue-Referenzen.

  • Was zuvor ein rvalue war (Literale, Objekte, die durch Umwandeln in Nicht-Referenztypen erzeugt wurden), wird nun zu einem Prvalue. Sie haben die gleiche Vorliebe wie xvalues ​​beim Überladen.

  • Was zuvor ein L-Wert war, bleibt ein L-Wert.

Und zwei Gruppierungen werden gemacht, um diejenigen zu erfassen, die qualifiziert werden können und unterschiedliche dynamische Typen haben können (glvalues) und solche, bei denen eine Überladung eine r-Referenzbindung bevorzugt (rvalues).


32
2018-06-17 02:20



Ich habe lange damit gerungen, bis ich auf die Erklärung von cppreference.com stieß 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 zitiere einige Teile der Seite:

Hauptkategorien

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

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

  • kann aus bewegt werden: move-Konstruktor, move-Zuweisungsoperator oder eine andere Funktionsüberladung, die die move-Semantik implementiert, kann an den Ausdruck binden.

Ausdrücke, die:

  • Identität haben und nicht verschoben werden können lvalue Ausdrücke;
  • Identität haben und von der aus aufgerufen werden kann xvalue Ausdrücke;
  • keine Identität haben und von der aus verschoben werden kann Prvalue Ausdrücke;
  • keine Identität haben und nicht verschoben werden können, werden nicht verwendet.

Wert

Ein Lvalue-Ausdruck ("left value") ist ein Ausdruck, der hat Identität und kann nicht verschoben werden.

rvalue (bis C ++ 11), prvalue (seit C ++ 11)

Ein prvalue ("reiner rvalue") Ausdruck ist ein Ausdruck, der hat keine Identität und kann aus bewegt werden.

xwert

Ein xvalue-Ausdruck ("ablaufender Wert") ist ein Ausdruck, der hat Identität und kann aus bewegt werden.

glvalue

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

rvalue (seit C ++ 11)

Ein rvalue-Ausdruck ("right value") ist ein Ausdruck, der entweder ein Pr-Wert oder ein X-Wert ist. Es kann aus bewegt werden. Es kann oder darf nicht Identität haben.


20
2017-08-30 15:45



Wie hängen diese neuen Kategorien mit den vorhandenen Kategorien rvalue und lvalue zusammen?

Ein C ++ 03 lvalue ist immer noch ein C ++ 11 lvalue, während ein C ++ 03 rvalue in C ++ 11 prvalue genannt wird.


15
2017-07-25 04:26



Ein Zusatz zu den ausgezeichneten Antworten oben, in einem Punkt, der mich verwirrte, selbst nachdem ich Stroustrup gelesen hatte und dachte, ich verstehe die Unterscheidung zwischen rvalue und lvalue. Wenn du siehst

int&& a = 3,

es ist sehr verlockend, das zu lesen int&& als ein Typ und schließe das a ist ein rvalue. Es ist nicht:

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

a hat einen Namen und ist ipso facto ein lvalue. Denk nicht an das && als Teil der Art von a; es ist nur etwas, was dir sagt was a darf sich binden.

Dies ist besonders wichtig für T&& Geben Sie Argumente in Konstruktoren ein. Wenn du schreibst

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

du wirst kopieren _t in t. Du brauchst

Foo::Foo(T&& _t) : t{std::move(_t)} {} wenn du dich bewegen willst. Würde mein Compiler mich gewarnt haben, als ich das ausließ move!


12
2018-04-21 06:58



In C ++ sind Variablen eine Art von l-Wert (ausgesprochen ell-Wert). Ein l-Wert ist ein Wert, der eine persistente Adresse (im Speicher) hat. Da alle Variablen Adressen haben, sind alle Variablen l-Werte. Der Name l-Wert ist entstanden, weil l-Werte die einzigen Werte sind, die auf der linken Seite einer Zuweisungsanweisung stehen können. Bei einer Zuweisung muss die linke Seite des Zuweisungsoperators ein l-Wert sein. Folglich eine Aussage wie 5 = 6; verursacht einen Kompilierfehler, weil 5 kein l-Wert ist. Der Wert 5 hat keinen Speicher und somit kann ihm nichts zugewiesen werden. 5 bedeutet 5 und sein Wert kann nicht neu zugewiesen werden. Wenn einem l-Wert ein Wert zugewiesen ist, wird der aktuelle Wert an dieser Speicheradresse überschrieben.

Das Gegenteil von l-Werten sind r-Werte (ausgeprägte arr-Werte). Ein r-Wert bezieht sich auf Werte, die keiner persistenten Speicheradresse zugeordnet sind. Beispiele für r-Werte sind einzelne Zahlen (z. B. 5, die zu 5 ausgewertet werden) und Ausdrücke (z. B. 2 + x, die den Wert der Variablen x plus 2 auswerten). R-Werte sind im Allgemeinen temporärer Natur und werden am Ende der Anweisung, in der sie auftreten, verworfen.

Hier ist ein Beispiel für einige Zuweisungsanweisungen, die zeigen, wie die r-Werte auswerten:

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

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

Schauen wir uns die letzte Anweisung oben genauer an, da sie die meiste Verwirrung verursacht.

x = x+1

In dieser Anweisung wird die Variable x in zwei verschiedenen Kontexten verwendet. Auf der linken Seite des Zuweisungsoperators wird "x" als ein l-Wert (Variable mit einer Adresse) verwendet, in der ein Wert gespeichert wird. Auf der rechten Seite des Zuweisungsoperators wird x ausgewertet, um einen Wert (in diesem Fall 7) zu erzeugen. Wenn C ++ die obige Anweisung auswertet, wird es wie folgt ausgewertet:

x = 7+1

Damit ist klar, dass C ++ den Wert 8 wieder in die Variable x zurückweist.

Vorläufig müssen Sie sich nicht viel um l-Werte oder r-Werte kümmern, aber wir werden später darauf zurückkommen, wenn wir beginnen, einige weitergehende Themen zu besprechen.

Der Schlüssel dazu ist, dass Sie auf der linken Seite der Zuweisung etwas haben müssen, das eine Speicheradresse darstellt (z. B. eine Variable). Alles auf der rechten Seite der Zuweisung wird ausgewertet, um einen Wert zu erzeugen.


-2