In C ++ 03 ist ein Ausdruck entweder ein r-Wert oder ein l-Wert .
In C ++ 11 kann ein Ausdruck ein:
- rWert
- lWert
- xvalue
- glvalue
- 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?
c++
expression
c++-faq
c++11
James McNellis
quelle
quelle
string("hello") = string("world")
.Antworten:
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 :
E
es 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 / dieE
verweist. Als weiteres Beispiel ist das Ergebnis des Aufrufs einer Funktion, deren Rückgabetyp eine lWertreferenz ist, ein lWert.]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.
quelle
Der FCD (n3092) hat eine hervorragende Beschreibung:
Ich schlage vor, Sie lesen den gesamten Abschnitt 3.10 L- und r-Werte .
Nochmal:
Die Semantik von Werten hat sich insbesondere mit der Einführung der Bewegungssemantik weiterentwickelt.
Damit konnte die Bewegungskonstruktion / -zuordnung definiert und unterstützt werden.
quelle
glvalue
alslvalue
undlvalue
als benennenplvalue
, um konsistent zu sein?Ich beginne mit Ihrer letzten Frage:
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.
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:
Beispiele mit Funktionen:
Vergessen Sie aber auch nicht, dass benannte rvalue-Referenzen lvalues sind:
quelle
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:
Wenn du das tust:
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.val
ist 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,
val
einen 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 vonFunc
oben)?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ßlichstd::move
zurü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::move
gibt einen xvalue zurück).quelle
&&
.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.X
eine Klasse sein;X foo();
eine Funktionsdeklaration undfoo() = X();
eine Codezeile sein. (Ich habe den zweiten Satz von Klammern infoo() = X();
meinem obigen Kommentar weggelassenIMHO, die beste Erklärung für seine Bedeutung gab uns Stroustrup + berücksichtigen Beispiele von Dániel Sándor und Mohan :
Stroustrup:
quelle
lvalue
s, alle anderen Literale sindprvalue
s. Genau genommen könnte man argumentieren, dass Nicht-String-Literale unbeweglich sein sollten, aber so wird der Standard nicht geschrieben.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:
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.
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.
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.
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
- eine Umwandlung in einen r-Wert-Verweis auf den Objekttyp oder
- ein Zugriffsausdruck für Klassenmitglieder, der ein nicht statisches Datenelement vom Nichtreferenztyp angibt, in dem der Objektausdruck ein x-Wert ist, oder
- 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.
Werte
Der Ausdruck E gehört genau dann zur Kategorie prvalue, wenn E weder zur Kategorie lvalue noch zur Kategorie xvalue gehört.
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.
quelle
struct As{void f(){this;}}
diethis
Variable ein Wert ist. Ich dachte,this
sollte 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.this
ist ein Wert, aber*this
ist ein Wert"www"
hat nicht immer die gleiche Adresse. Es ist ein Wert, weil es ein Array ist .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
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
const
im 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 .
quelle
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:
quelle
Ein C ++ 03-Wert ist immer noch ein C ++ 11-Wert, während ein C ++ 03-Wert in C ++ 11 als Wert bezeichnet wird.
quelle
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 diesa
ein Wert ist. Es ist nicht:a
hat einen Namen und ist ipso facto ein Wert. Betrachten Sie das nicht&&
als Teil der Art vona
; Es sagt dir nur, worana
man sich binden darf.Dies ist insbesondere für
T&&
Typargumente in Konstruktoren von Bedeutung. Wenn du schreibstFoo::Foo(T&& _t) : t{_t} {}
Sie kopieren
_t
int
. Du brauchstFoo::Foo(T&& _t) : t{std::move(_t)} {}
wenn du umziehen willst. Würde mich mein Compiler warnen, wenn ich das weglassemove
?quelle
a
darf 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 vona
ist hier irrelevant, nicht wahr? Die Zeilen wären die gleichen, wenn siea
deklariert würdenint a
. Der eigentliche Hauptunterschied besteht darin, dass in Zeile 1 a nichtconst
an 3Da 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:
Gemischte Kategorien:
Jetzt können wir (fast) alle Beispiele aus der Referenz zur Wertekategorie reproduzieren .
Hier einige Beispiele mit C ++ 17 (für terse static_assert):
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.
quelle
#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 seidIS_GLVALUE
.