c++ - Was sind rvalues, lvalues, xvalues, glvalues und prvalues?




c++ xvalue (8)

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. Zu 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 explizit 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, 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 nicht darum gebeten, es zu bewegen. Oh, du könntest sagen, dass das && ein Hinweis gewesen sein sollte, aber das ändert nichts an der Tatsache, dass es die Regel gebrochen hat. val ist nicht temporär, da temporäre Namen keine Namen haben. Du magst die Lebensdauer des Temporären verlängert haben, aber das bedeutet, dass es nicht vorübergehend ist ; Es ist wie jede andere Stapelvariable.

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

Die naheliegende Lösung besteht darin, val lval zu machen. 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&& dasselbe bedeutet. 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 oben)?

Es ist kein Lvalue, da Sie sich nicht von einem Lvalue bewegen können. Und wir müssen uns bewegen können, indem wir ein && ; wie sonst könnten Sie explizit sagen, etwas zu bewegen? Das ist es, was std::move zurückgibt. Es ist kein rvalue (alter Stil), weil es auf der linken Seite einer Gleichung sein kann (die Dinge sind eigentlich etwas 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 Lvalue behandeln können, außer dass es implizit von diesem Wert bewegt werden kann . Wir nennen es einen xvalue .

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

  • Ein Prvalue ist eigentlich nur der neue Name für den vorherigen Typ von rvalue, dh sie sind die rvalues, die keine xvalues ​​sind .

  • Die 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).

In C ++ 03 ist ein Ausdruck entweder ein R-Wert oder ein L-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? WG21 die WG21 Götter nur, uns WG21 zu verwirren?

Was sind diese neuen Kategorien von Ausdrücken?

Der FCD (n3092) hat eine ausgezeichnete Beschreibung:

- 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 ist * E ein lvalue-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 L-Wert-Referenz ist, ein L-Wert. -End-Beispiel]

- Ein xvalue (ein "eXpiring" -Wert) bezieht sich auch auf ein Objekt, normalerweise am Ende seiner Lebensdauer (so dass beispielsweise seine Ressourcen verschoben werden können). Ein xvalue ist das Ergebnis bestimmter Ausdrücke mit rvalue-Referenzen (8.3.2). [Beispiel: Das Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp eine rvalue-Referenz ist, ist ein xvalue. -End-Beispiel]

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

- Ein rvalue (historisch gesehen, weil rvalues ​​auf der rechten Seite eines Zuweisungsausdrucks stehen könnte) ist ein xvalue, ein temporäres Objekt (12.2) oder Unterobjekt davon oder ein Wert, der keinem Objekt zugeordnet ist.

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

Jeder Ausdruck gehört zu genau einer der grundlegenden Klassifikationen in dieser Taxonomie: lvalue, xvalue oder prvalue. Diese Eigenschaft eines Ausdrucks wird als Wertkategorie bezeichnet. [Anmerkung: Die Erörterung jedes eingebauten Operators in Abschnitt 5 zeigt die Kategorie des Wertes, den er liefert, und die Wertekategorien der Operanden, die er erwartet. Zum Beispiel erwarten die eingebauten Zuweisungsoperatoren, dass der linke Operand ein L-Wert ist und dass der rechte Operand ein Pr-Wert ist und als Ergebnis einen L-Wert ergibt. Benutzerdefinierte Operatoren sind Funktionen, und die Kategorien von Werten, die sie erwarten und liefern, werden durch ihre Parameter und Rückgabetypen bestimmt. -Ende beachten

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:

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.


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 ist * E ein lvalue-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 L-Wert-Referenz ist, ein L-Wert. -End-Beispiel]
  • Ein xvalue (ein "eXpiring" -Wert) bezieht sich auch auf ein Objekt, normalerweise am Ende seiner Lebensdauer (so dass beispielsweise seine Ressourcen verschoben werden können). Ein xvalue ist das Ergebnis bestimmter Ausdrücke mit rvalue-Referenzen (8.3.2). [Beispiel: Das Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp eine rvalue-Referenz ist, ist ein xvalue. -End-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 einer der grundlegenden Klassifikationen in dieser Taxonomie: lvalue, xvalue oder prvalue. Diese Eigenschaft eines Ausdrucks wird als Wertkategorie bezeichnet.

Aber ich bin mir nicht ganz sicher, ob dieser Unterabschnitt ausreicht, um die Konzepte klar zu verstehen, denn "normalerweise" ist nicht wirklich allgemein, "am Ende seines Lebens" ist nicht wirklich konkret, "mit Bezug auf 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 eine sehr nützliche Faustregel veröffentlicht, 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 & oder const T &, usw.), ist dieser Ausdruck ein L-Wert.
  • Ansonsten ist der Ausdruck ein rvalue. Konzeptionell (und typischerweise auch tatsächlich) entsprechen rvalues ​​temporären Objekten, wie sie von Funktionen zurückgegeben oder durch implizite Typkonvertierungen erzeugt werden. Die meisten Literalwerte (z. B. 10 und 5.3) sind ebenfalls rvalues.

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 wurde gesagt, dass eine unbenannte rvalue-Referenz einen rvalue auswertet, so dass eine Überladungsauflösung rvalue-Referenzbindungen bevorzugen würde, was dazu führen würde, dass move-Konstruktoren über Kopierkonstruktoren ausgewählt würden. Es wurde jedoch festgestellt, dass dies überall Probleme verursacht, zum Beispiel mit dynamischen 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. Es ist aber beabsichtigt, dass const im Rvalue-Referenzfall gilt, weil wir hier auf Objekte (= Speicher!) Verweisen, und das Fallenlassen von Const aus Nicht-Klassen-Rvalues ​​hauptsächlich deshalb, weil es kein Objekt 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 zu 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 verschiedene dynamische Typen ( glvalues ) haben können, und solche, bei denen die Überladung eine rvalue-Referenzbindung ( rvalues ) bevorzugt.


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.

Zitat n3055 :

  • Ein lvalue (historisch so, 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 ist *E ein lvalue-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 lvalue-Referenz ist, ein lvalue.]
  • Ein xvalue (ein "eXpiring" -Wert) bezieht sich auch auf ein Objekt, normalerweise am Ende seiner Lebensdauer (so dass beispielsweise seine Ressourcen verschoben werden können). Ein xvalue ist das Ergebnis bestimmter Arten von Ausdrücken, die rvalue-Referenzen enthalten. [Beispiel: Das Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp eine rvalue-Referenz ist, ist ein xvalue.]
  • 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, ein temporäres Objekt oder Unterobjekt davon oder ein Wert, der keinem Objekt zugeordnet ist.
  • 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 keine Referenz ist, ist ein 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.


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
}

In C++, variables are a type of l-value (pronounced ell-value). An l-value is a value that has a persistent address (in memory). Since all variables have addresses, all variables are l-values. The name l-value came about because l-values are the only values that can be on the left side of an assignment statement. When we do an assignment, the left-hand side of the assignment operator must be an l-value. Consequently, a statement like 5 = 6; will cause a compile error, because 5 is not an l-value. The value of 5 has no memory, and thus nothing can be assigned to it. 5 means 5, and its value can not be reassigned. When an l-value has a value assigned to it, the current value at that memory address is overwritten.

The opposite of l-values are r-values (pronounced arr-values). An r-value refers to values that are not associated with a persistent memory address. Examples of r-values are single numbers (such as 5, which evaluates to 5) and expressions (such as 2 + x, which evaluates to the value of variable x plus 2). r-values are generally temporary in nature and are discarded at the end of the statement in which they occur.

Here is an example of some assignment statements, showing how the r-values evaluate:

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.

Let's take a closer look at the last assignment statement above, since it causes the most confusion.

x = x+1

In this statement, the variable x is being used in two different contexts. On the left side of the assignment operator, “x” is being used as an l-value (variable with an address) in which to store a value. On the right side of the assignment operator, x is evaluated to produce a value (in this case, 7). When C++ evaluates the above statement, it evaluates as:

x = 7+1

Which makes it obvious that C++ will assign the value 8 back into variable x.

For the time being, you don't need to worry about l-values or r-values much, but we'll return to them later when we start discussing some more advanced topics.

The key takeaway here is that on the left side of the assignment, you must have something that represents a memory address (such as a variable). Everything on the right side of the assignment will be evaluated to produce a value.


One addendum to the excellent answers above, on a point that confused me even after I had read Stroustrup and thought I understood the rvalue/lvalue distinction. When you see

int&& a = 3 ,

it's very tempting to read the int&& as a type and conclude that a is an rvalue. It's not:

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

a has a name and is ipso facto an lvalue. Don't think of the && as part of the type of a ; it's just something telling you what a is allowed to bind to.

This matters particularly for T&& type arguments in constructors. If you write

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

you will copy _t into t . Du brauchst

Foo::Foo(T&& _t) : t{std::move(_t)} {} if you want to move. Would that my compiler warned me when I left out the move !







c++11