In C++ 03 ist ein Ausdruck entweder ein Wert oder ein Wert .
In C++ 11 kann ein Ausdruck sein:
Aus zwei Kategorien sind fünf Kategorien geworden.
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 eine Unterscheidung zwischen Ausdrücken, die verschoben werden können, und in welche Richtung.
Nach dem, was ich auf der Grundlage des Entwurfs vermute, bleibt die R/L-Wertunterscheidung gleich, nur im Kontext von sich bewegenden Dingen wird es chaotisch.
Werden sie gebraucht Wahrscheinlich nicht, wenn wir auf die neuen Funktionen verzichten wollen. Aber um eine bessere Optimierung zu ermöglichen, sollten wir sie wahrscheinlich annehmen.
Zitiert n3055 :
E
ein Ausdruck vom Zeigertyp ist, dann ist *E
ein Wertausdruck, der sich auf das Objekt oder die Funktion bezieht, auf das/die E
verweist. Als weiteres Beispiel ist das Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp eine lWert-Referenz ist, ein lWert.] Das betreffende Dokument ist eine hervorragende Referenz für diese Frage, da es die genauen Änderungen in der Norm zeigt, die infolge der Einführung der neuen Nomenklatur eingetreten sind.
Was sind diese neuen Kategorien von Ausdrücken?
Die FCD (n3092) hat eine hervorragende Beschreibung:
- Ein lWert (historisch gesehen so genannt, weil lWerte auf der linken Seite eines Zuweisungsausdrucks stehen können) bezeichnet eine Funktion oder ein Objekt. [Beispiel: Wenn E ein Ausdruck vom Zeigertyp ist, dann ist * E ein Wertausdruck, 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 lWert-Referenz ist, ein lWert. —End Beispiel]
- 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 Ausdrücke mit r-Wert-Referenzen (8.3.2). [Beispiel: Das Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp eine rWert-Referenz ist, ist ein xWert. —End Beispiel]
- Ein glvalue ("generalisierter" lvalue) ist ein lvalue oder ein xvalue.
- Ein r-Wert (historisch gesehen 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 prvalue ("reiner" rvalue) ist ein rvalue, der kein xvalue ist. [Beispiel: Das Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp keine Referenz ist, ist ein Wert. Der Wert eines Literal wie 12, 7.3e5 oder true ist ebenfalls ein Wert. —End Beispiel]
Jeder Ausdruck gehört zu genau einer der grundlegenden Klassifikationen in dieser Taxonomie: Ivalue, Xvalue oder Prvalue. Diese Eigenschaft eines Ausdrucks wird als Wertkategorie bezeichnet. [Anmerkung: Die Beschreibung jedes eingebauten Operators in Abschnitt 5 gibt die Kategorie des Werts an, den er liefert, und die Wertkategorien der Operanden, die er erwartet. Beispielsweise erwarten die integrierten Zuweisungsoperatoren, dass der linke Operand ein I-Wert und der rechte Operand ein Pr-Wert ist, und ergeben als Ergebnis einen I-Wert. Benutzerdefinierte Operatoren sind Funktionen, und die Kategorien von Werten, die sie erwarten und liefern, werden durch ihre Parameter und Rückgabetypen bestimmt. - Ende der Notiz
Ich schlage vor, Sie lesen den gesamten Abschnitt 3.10 L-Werte und r-Werte .
In welcher Beziehung stehen diese neuen Kategorien zu den vorhandenen Kategorien rvalue und lvalue?
Nochmal:
Stimmen die Kategorien rvalue und lvalue in C++ 0x mit denen in C++ 03 überein?
Die Semantik der Werte hat sich insbesondere mit der Einführung der Bewegungssemantik weiterentwickelt.
Warum werden diese neuen Kategorien benötigt?
Damit konnte die Umzugskonstruktion/-zuordnung definiert und unterstützt werden.
Ich werde mit Ihrer letzten Frage beginnen:
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 Überlastungsauflösung geht. Andere Regeln unterscheiden zwischen glvalue und prvalue. Sie können beispielsweise einen glWert mit einem unvollständigen oder abstrakten Typ haben, aber es gibt keinen prWert mit einem unvollständigen oder abstrakten Typ. Bevor wir diese Terminologie hatten, enthielten die Regeln, die tatsächlich zwischen glvalue/prvalue und lvalue/rvalue unterscheiden müssen, entweder unbeabsichtigt falsche oder viele Erklärungen und Ausnahmen von der Regel a la "... es sei denn, der rvalue ist auf unbenannte zurückzuführen rWertreferenz ... ". 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 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 Wertkategorie für unbenannte R-Wert-Referenzen. Jeder Ausdruck ist einer dieser drei: Ivalue, Xvalue, Prvalue. 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
}
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 davon gut sind) die Antwort auf diese spezielle Frage wirklich erfassen. Ja, diese und ähnliche Kategorien ermöglichen die Bewegungssemantik, 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 ohne Zweifel sicher ist.
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 Bewegung kein Problem. 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:
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 rWert-Referenz an den Konstruktor übergeben, und daher wird sie an den Konstruktor gebunden, der eine rWert-Referenz verwendet. Das ist offensichtlich.
Es gibt nur ein Problem damit; Sie haben nicht darum gebeten , es zu verschieben. Oh, du könntest sagen, dass der &&
ein Hinweis sein sollte, aber das ändert nichts an der Tatsache, dass er die Regel gebrochen hat. val
ist keine temporäre Datei, da temporäre Dateien keine Namen haben. Möglicherweise haben Sie die Lebensdauer des temporären Objekts verlängert, dies bedeutet jedoch, dass es nicht temporär ist . Es ist genau wie jede andere Stapelvariable.
Wenn es sich nicht um ein temporäres Objekt handelt und Sie nicht darum gebeten haben, es zu verschieben, ist das Verschieben falsch.
Die naheliegende Lösung besteht darin, val
zu einem Wert zu machen. Das bedeutet, dass Sie sich nicht davon entfernen können. OK, gut; Es heißt, also ist es ein Wert.
Wenn Sie das getan haben, können Sie nicht mehr sagen, dass SomeType&&
überall dasselbe bedeutet. Sie haben jetzt eine Unterscheidung zwischen benannten R-Wert-Referenzen und unbenannten R-Wert-Referenzen getroffen. Nun, benannte rvalue-Referenzen sind lvalues; das war unsere obige Lösung. Wie nennen wir also unbenannte rvalue-Referenzen (den Rückgabewert von Func
oben)?
Es ist kein LWERT, weil man sich nicht von einem LWERT bewegen kann. Und wir brauchen , um uns bewegen zu können, indem wir einen &&
zurückgeben; wie könnte man sonst explizit sagen, etwas zu bewegen? Das ist es doch, was std::move
zurückgibt. Es ist kein R-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 , von dem aus er implizit 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 Typ von rvalue, d. H. Es handelt sich um die rvalues, bei denen keine xvalues sind.
GlWerte sind die Vereinigung von xWerten und lWerten in einer Gruppe, da sie viele Eigenschaften gemeinsam haben.
Es kommt also wirklich auf die x-Werte und die Notwendigkeit an, die Bewegung auf genau und nur bestimmte Stellen zu beschränken. Diese Orte werden durch die rWert-Kategorie definiert. prvalues sind die impliziten Bewegungen und xvalues sind die expliziten Bewegungen (std::move
gibt einen xvalue zurück).
IMHO, die beste Erklärung über 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 Durcheinander oder beides. Ich habe die Mittagspause damit verbracht, eine Analyse durchzuführen, um festzustellen, welche der Eigenschaften (der Werte) unabhängig sind. Es gab 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 verlassenDies führte mich zu der Schlussfolgerung, 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 werdenim
: Hat Identität und kann verschoben werden von (z. B. dem Ergebnis des Umsetzens eines Werts in eine R-Wert-Referenz)
Im
: hat keine Identität und kann verschoben werden.Die vierte Möglichkeit,
IM
(hat keine Identität und kann nicht verschoben werden), ist inC++
(oder, glaube ich, in einer anderen Sprache) nicht nützlich.Zusätzlich zu diesen drei grundlegenden Werteklassifikationen haben wir zwei offensichtliche Verallgemeinerungen, die den beiden unabhängigen Eigenschaften entsprechen:
i
: hat Identitätm
: kann von verschoben werdenDies veranlasste mich, dieses Diagramm an die Tafel zu setzen:
Benennung
Ich bemerkte, dass wir nur eingeschränkte Namensfreiheit hatten: Die beiden Punkte links (mit
iM
undi
bezeichnet) sind das, was Leute mit mehr oder weniger Formalitätlvalues
genannt haben, und die beiden Punkte rechts (mitm
undIm
bezeichnet) haben Personen mit mehr oder weniger Formalitätrvalues
genannt. Dies muss sich in unserer Benennung widerspiegeln. Das heißt, das linke "Bein" desW
sollte Namen haben, die mitlvalue
zusammenhängen, und das rechte "Bein" desW
sollte Namen haben, die mitrvalue.
zusammenhängen dass sich diese ganze Diskussion/dieses Problem aus der Einführung von rWertreferenzen und der Bewegungssemantik ergibt. Diese Begriffe existieren in Stracheys Welt einfach nicht und bestehen nur ausrvalues
undlvalues
. Jemand hat beobachtet, dass die Ideen das
- Jedes
value
ist entweder einlvalue
oder einrvalue
- Ein
lvalue
ist keinrvalue
und einrvalue
ist keinlvalue
sind tief in unser Bewusstsein eingebettet, sehr nützliche Eigenschaften, und Spuren dieser Dichotomie finden sich überall im Normentwurf. Wir waren uns alle einig, dass wir diese Eigenschaften erhalten (und präzisieren) sollten. Dies schränkte unsere Namensauswahl weiter ein. Ich bemerkte, dass der Standardtext der Bibliothek
rvalue
bedeutetm
(die Verallgemeinerung), so dass der rechte untere Punkt desW
die Erwartung und den Text der Standardbibliothek bewahrt. solltervalue.
heißenDies führte zu einer gezielten Diskussion der Benennung. Zuerst mussten wir uns für
lvalue.
entscheiden. Solltelvalue
iM
bedeuten oder für die Verallgemeinerungi
? Unter der Leitung von Doug Gregor haben wir die Stellen in der Kernsprache aufgelistet, an denen das Wortlvalue
für das eine oder andere qualifiziert war. Eine Liste wurde erstellt und in den meisten Fällen und im schwierigsten/sprödesten Text bedeutetlvalue
derzeitiM
. Dies ist die klassische Bedeutung von lvalue, weil "in den alten Tagen" nichts bewegt wurde;move
ist ein neuartiger Begriff inC++0x
. Wenn Sie den oberen linken Punkt vonW
lvalue
benennen, erhalten Sie die Eigenschaft, dass jeder Wert einlvalue
oder einrvalue
ist, jedoch nicht beides.Der obere linke Punkt von
W
istlvalue
und der untere rechte Punkt istrvalue.
Was bedeutet das für den unteren linken und den oberen rechten Punkt? Der untere linke Punkt ist eine Verallgemeinerung des klassischen L-Wertes, die eine Bewegung zulässt. Es ist also eingeneralized lvalue.
Wir haben esglvalue.
genannt. Sie können über die Abkürzung streiten, aber (glaube ich) nicht mit der Logik. Wir gingen davon aus, dass bei ernsthaftem Gebrauchgeneralized lvalue
irgendwie sowieso abgekürzt werden würde, also sollten wir es sofort tun (oder es besteht die Gefahr von Verwirrung). Der obere rechte Punkt des W ist weniger allgemein als der untere rechte (wird jetzt wie immerrvalue
genannt). Dieser Punkt repräsentiert den ursprünglichen reinen Begriff eines Objekts, von dem aus Sie sich bewegen können, da es nicht erneut referenziert werden kann (außer von einem Destruktor). Ich mochte die Phrasespecialized rvalue
im Gegensatz zugeneralized lvalue
, aberpure rvalue
, abgekürzt alsprvalue
, siegte (und wahrscheinlich zu Recht). Das linke Bein des W ist alsolvalue
undglvalue
und das rechte Bein istprvalue
undrvalue.
Übrigens ist jeder Wert entweder ein Wert oder ein Wert, aber nicht beide.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 Menschen, die mit dem (Entwurfs-) Standardtext arbeiten, werden aber wahrscheinlich nicht zu einem bekannten Namen. Wir haben keine wirklichen Einschränkungen in Bezug auf die Benennung gefunden, die uns leiten könnten. Deshalb haben wir "x" für das Zentrum, das Unbekannte, das Fremde, das Nur-Xpert oder sogar für die X-Bewertung ausgewählt.
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:
Wenn wir die Konzepte der neuen Ausdruckswertkategorien verstehen möchten, müssen wir uns bewusst sein, dass es r- und l-Wertereferenzen gibt. Es ist besser zu wissen, dass rWerte an nicht konstante rWertreferenzen ü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 Wertkategorien machen, wenn wir den Unterabschnitt Lvalues and rvalues aus dem Arbeitsentwurf N3337 zitieren (der dem veröffentlichten ISOC++ 11-Standard am ähnlichsten ist).
3.10 L-Werte und r-Werte [basic.lval]
1 Ausdrücke werden gemäß der Taxonomie in Abbildung 1 kategorisiert.
- Ein lWert (historisch gesehen so genannt, weil lWerte auf der linken Seite eines Zuweisungsausdrucks stehen können) bezeichnet eine Funktion oder ein Objekt. [Beispiel: Wenn E ein Ausdruck vom Zeigertyp ist, dann ist * E ein Wertausdruck, 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 lWert-Referenz ist, ein lWert. —End Beispiel]
- 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 Ausdrücke mit r-Wert-Referenzen (8.3.2). [Beispiel: Das Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp eine rWert-Referenz ist, ist ein xWert. —End Beispiel]
- Ein glvalue ("generalisierter" lvalue) ist ein lvalue oder ein xvalue.
- Ein rWert (historisch gesehen so genannt, weil rWerte auf der rechten Seite eines Zuweisungsausdrucks erscheinen könnten) ist ein xWert, a
Temporäres Objekt (12.2) oder Unterobjekt davon oder ein Wert, der nicht ist
mit einem Objekt verbunden.- Ein prvalue ("reiner" rvalue) ist ein rvalue, der kein xvalue ist. [Beispiel: Das Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp nicht a ist
Referenz ist ein Wert. Der Wert eines Literal wie 12, 7.3e5 oder
wahr ist auch ein Wert. —End Beispiel]Jeder Ausdruck gehört zu genau einer der grundlegenden Klassifikationen in dieser Taxonomie: Ivalue, 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, "kurz vor dem Ende seiner Lebensdauer" nicht wirklich konkret ist und "Verweise auf Werte" nicht wirklich klar ist. und "Beispiel: Das Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp eine rWert-Referenz ist, ist ein xWert." klingt wie eine Schlange seinen Schwanz beißt.
Jeder Ausdruck gehört zu genau einer Primärwertkategorie. Diese Wertekategorien sind lvalue-, xvalue- und prvalue-Kategorien.
Der Ausdruck E gehört genau dann zur Wertkategorie, wenn sich E auf eine Entität bezieht, die BEREITS 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; // 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;
}
Der Ausdruck E gehört genau dann zur Kategorie xvalue, wenn er ist.
- das Ergebnis des impliziten oder expliziten Aufrufs einer Funktion, deren Rückgabetyp eine R-Wert-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 Wert, der auf den Objekttyp verweist, 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 Klassenmitglied-Zugriffsausdruck, der ein nicht statisches Datenmitglied eines Nichtreferenztyps bezeichnet, 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 auf einen Mitgliedsausdruck, in dem der erste Operand ein x-Wert und der zweite Operand ein Zeiger auf ein Datenmitglied ist.
Beachten Sie, dass die oben genannten Regeln bewirken, dass benannte R-Wert-Verweise auf Objekte als I-Werte und unbenannte R-Wert-Verweise auf Objekte als X-Werte behandelt werden. rWertverweise 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;
}
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;
}
Es gibt zwei weitere wichtige Mischwertkategorien. Diese Wertkategorien sind rvalue- und glvalue-Kategorien.
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 Kategorie rvalue 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.
Der Ausdruck E gehört genau dann zur Kategorie glvalue, wenn E zur Kategorie lvalue oder zur Kategorie xvalue gehört.
Scott Meyer hat veröffentlicht eine sehr nützliche Faustregel, um r-Werte von l-Werten zu unterscheiden.
- Wenn Sie die Adresse eines Ausdrucks übernehmen können, ist der Ausdruck ein lWert.
- Wenn der Typ eines Ausdrucks eine Wertreferenz ist (z. B. T & oder const T & usw.), ist dieser Ausdruck ein Wert.
- Andernfalls ist der Ausdruck ein Wert. Konzeptionell (und in der Regel 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.
Die Kategorien in C++ 03 sind zu eingeschränkt, um die Einführung von rWert-Referenzen in Ausdrucksattribute korrekt zu erfassen.
Bei der Einführung wurde gesagt, dass eine unbenannte rvalue-Referenz einen rvalue ergibt, sodass die Überladungsauflösung rvalue-Referenzbindungen bevorzugt, wodurch Move-Konstruktoren gegenüber Copy-Konstruktoren ausgewählt werden. Es hat sich jedoch herausgestellt, dass dies Probleme verursacht, z. B. mit Dynamic Types und mit Qualifikationen.
Um dies zu zeigen, ziehen Sie in Betracht
int const&& f();
int main() {
int &&i = f(); // disgusting!
}
Bei Entwürfen vor xvalue war dies zulässig, da in C++ 03 Werte von Nichtklassentypen niemals cv-qualifiziert sind. Es ist jedoch beabsichtigt, dass const
für den Fall rvalue-reference gilt, da hier do auf Objekte (= Speicher!) Verwiesen wird und const von Nicht-Klassen gelöscht wird rvalues liegt hauptsächlich daran, dass sich kein Objekt in der Nähe befindet.
Das Problem bei dynamischen Typen ist von ähnlicher Natur. In C++ 03 haben Werte vom Typ class einen bekannten dynamischen Typ - es ist der statische Typ dieses Ausdrucks. Denn um es anders zu haben, brauchen Sie Referenzen oder Dereferenzen, die zu einem Wert ausgewertet werden. Dies gilt nicht für unbenannte R-Wert-Referenzen, sie können jedoch polymorphes Verhalten zeigen. Also, um es zu lösen,
unbenannte rvalue-Referenzen werden zu xvalues. Sie können qualifiziert sein und möglicherweise einen anderen dynamischen Typ haben. Sie bevorzugen, wie beabsichtigt, rWert-Referenzen während des Überladens und binden sich nicht an nicht konstante lWert-Referenzen.
Was zuvor ein rWert war (Literale, Objekte, die durch Umwandlungen in Nichtreferenztypen erstellt wurden), wird jetzt zu prWert. Sie haben die gleiche Präferenz wie x-Werte beim Überladen.
Was früher ein lWert war, bleibt ein lWert.
Und zwei Gruppierungen werden durchgeführt, um diejenigen zu erfassen, die qualifiziert werden können und unterschiedliche dynamische Typen haben können (glvalues), und diejenigen, bei denen Überladung eine rWertreferenzbindung bevorzugt (rvalues).
Ich habe lange damit gekämpft, bis ich auf die Erklärung von cppreference.com für Wertkategorien stieß.
Eigentlich ist es ziemlich einfach, aber ich finde, dass es oft so erklärt wird, dass man es sich nur schwer merken kann. Hier wird es sehr schematisch erklärt. Ich zitiere einige Teile der Seite:
Primäre Kategorien
Die Primärwertkategorien 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 Funktionen, die sie identifizieren (direkt oder indirekt erhalten). ;
kann von verschoben werden: move -Konstruktor, move-Zuweisungsoperator oder eine andere Funktionsüberladung, die die Verschiebungssemantik implementiert, kann an den Ausdruck gebunden werden.
Ausdrücke, die:
- haben Identität und können nicht verschoben werden heißen lWertausdrücke ;
- haben Identität und können verschoben werden von werden aufgerufen xvalue Ausdrücke ;
- habe keine Identität und kann verschoben werden von werden aufgerufen prvalue expressions ;
- keine Identität haben und nicht verschoben werden können, werden nicht verwendet.
wert
Ein lvalue-Ausdruck ("left value") ist ein Ausdruck, bei dem die Identität hat und nicht von verschoben werden kann.
rvalue (bis C++ 11), prvalue (seit C++ 11)
Ein prvalue-Ausdruck ("pure rvalue") ist ein Ausdruck, bei dem keine Identität hat und von verschoben werden kann.
xvalue
Ein xvalue-Ausdruck ("Expiring Value") ist ein Ausdruck, bei dem die Identität hat und von verschoben werden kann.
glvalue
Ein glvalue-Ausdruck ("generalized lvalue") ist ein Ausdruck, der entweder lvalue oder xvalue ist. Es hat Identität . Es kann verschoben werden oder nicht.
rvalue (seit C++ 11)
Ein rvalue-Ausdruck ("rechter Wert") ist ein Ausdruck, der entweder ein prvalue-Ausdruck oder ein xvalue-Ausdruck ist. Es kann von verschoben werden. Es kann Identität haben oder nicht.
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 prvalue bezeichnet wird.
Ein Nachtrag zu den hervorragenden Antworten oben in einem Punkt, der mich verwirrte, selbst nachdem ich Stroustrup gelesen und geglaubt hatte, den Unterschied zwischen Wert und Wert zu verstehen. Wenn du siehst
int&& a = 3
,
es ist sehr verlockend, den int&&
als Typ zu lesen und daraus zu schließen, dass a
ein Wert ist. 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 Wert. Denken Sie nicht an den &&
als Teil der Art von a
; Es ist nur etwas, das Ihnen sagt, an was a
gebunden werden darf.
Dies gilt insbesondere für Argumente vom Typ T&&
in Konstruktoren. Wenn du schreibst
Foo::Foo(T&& _t) : t{_t} {}
sie werden _t
in t
kopieren. Du brauchst
Foo::Foo(T&& _t) : t{std::move(_t)} {}
, wenn Sie sich bewegen möchten. Würde mich mein Compiler warnen, wenn ich das move
weglasse?
Da in den vorherigen Antworten die Theorie hinter den Wertkategorien ausführlich behandelt wurde, 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 die Eingabe ...
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 cppreference on value category 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(trunk). Clang 7.0.0 and msvc 19.16 are doing fine.
static_assert(IS_XVALUE(S().x));
Die gemischten Kategorien sind etwas langweilig, sobald Sie die Hauptkategorie herausgefunden haben.
Weitere Beispiele (und Experimente) finden Sie unter Link im Compiler-Explorer . Aber lesen Sie nicht die Versammlung. Ich habe viele Compiler hinzugefügt, um sicherzustellen, dass es auf allen gängigen Compilern funktioniert.