Warum ist es nicht erlaubt, eine nicht konstante Referenz auf ein temporäres Objekt zu erhalten, welche Funktion getx()
zurückgibt? Dies ist natürlich durch C ++ Standard verboten, aber ich interessiere mich für den Zweck einer solchen Einschränkung, nicht für einen Verweis auf den Standard.
struct X
{
X& ref() { return *this; }
};
X getx() { return X();}
void g(X & x) {}
int f()
{
const X& x = getx(); // OK
X& x = getx(); // error
X& x = getx().ref(); // OK
g(getx()); //error
g(getx().ref()); //OK
return 0;
}
- Es ist klar, dass die Lebensdauer des Objekts nicht die Ursache sein kann, da eine ständige Bezugnahme auf ein Objekt nicht verboten ist durch C ++ Standard .
- Es ist klar, dass das temporäre Objekt im obigen Beispiel nicht konstant ist, da Aufrufe nicht konstanter Funktionen zulässig sind. Zum Beispiel,
ref()
könnte das temporäre Objekt geändert werden. - Darüber hinaus
ref()
können Sie den Compiler täuschen und einen Link zu diesem temporären Objekt erhalten , wodurch unser Problem gelöst wird.
Zusätzlich:
Sie sagen, dass "das Zuweisen eines temporären Objekts zur const-Referenz die Lebensdauer dieses Objekts verlängert" und "über nicht-const-Referenzen wird jedoch nichts gesagt". Meine zusätzliche Frage . Verlängert die folgende Zuweisung die Lebensdauer des temporären Objekts?
X& x = getx().ref(); // OK
Antworten:
Aus diesem Visual C ++ - Blogartikel über rWertreferenzen :
Grundsätzlich sollten Sie nicht versuchen, temporäre Objekte zu ändern, da sie temporäre Objekte sind und jeden Moment sterben werden. Der Grund, warum Sie nicht-const-Methoden aufrufen dürfen, ist, dass Sie gerne einige "dumme" Dinge tun können, solange Sie wissen, was Sie tun, und dies explizit tun (z. B. mit reinterpret_cast). Wenn Sie jedoch eine temporäre Referenz an eine nicht konstante Referenz binden, können Sie sie "für immer" weitergeben, damit Ihre Manipulation des Objekts verschwindet, da Sie irgendwo auf dem Weg völlig vergessen haben, dass dies eine temporäre Referenz war.
Wenn ich Sie wäre, würde ich das Design meiner Funktionen überdenken. Warum akzeptiert g () eine Referenz, ändert es den Parameter? Wenn nein, machen Sie es zu einem konstanten Verweis. Wenn ja, warum versuchen Sie, es vorübergehend zu übergeben? Ist es Ihnen egal, dass es sich um ein temporäres Element handelt, das Sie ändern? Warum kehrt getx () überhaupt vorübergehend zurück? Wenn Sie uns Ihr reales Szenario und das, was Sie erreichen möchten, mitteilen, erhalten Sie möglicherweise einige gute Vorschläge dazu.
Gegen die Sprache zu gehen und den Compiler zu täuschen, löst selten Probleme - normalerweise entstehen Probleme.
Bearbeiten: Fragen im Kommentar beantworten: 1)
X& x = getx().ref(); // OK when will x die?
- Ich weiß es nicht und es ist mir egal, denn genau das meine ich mit "gegen die Sprache gehen". In der Sprache heißt es: "Provisorien sterben am Ende der Aussage, es sei denn, sie sind an eine konstante Referenz gebunden. In diesem Fall sterben sie, wenn die Referenz außerhalb des Geltungsbereichs liegt." Bei Anwendung dieser Regel scheint x bereits zu Beginn der nächsten Anweisung tot zu sein, da es nicht an die const-Referenz gebunden ist (der Compiler weiß nicht, was ref () zurückgibt). Dies ist jedoch nur eine Vermutung.2) Ich habe den Zweck klar angegeben: Sie dürfen keine temporären Elemente ändern, da dies einfach keinen Sinn ergibt (Ignorieren von C ++ 0x-Wertreferenzen). Die Frage "Warum darf ich dann Nicht-Konstanten-Mitglieder anrufen?" ist eine gute, aber ich habe keine bessere Antwort als die, die ich oben bereits angegeben habe.
3) Nun, wenn ich mit x
X& x = getx().ref();
am Ende der Aussage Recht habe , liegen die Probleme auf der Hand.Aufgrund Ihrer Fragen und Kommentare denke ich nicht, dass selbst diese zusätzlichen Antworten Sie zufriedenstellen werden. Hier ist ein letzter Versuch / eine letzte Zusammenfassung: Das C ++ - Komitee entschied, dass es nicht sinnvoll ist, temporäre Elemente zu ändern, weshalb die Bindung an nicht konstante Verweise nicht zulässig war. Möglicherweise waren auch einige Compiler-Implementierungen oder historische Probleme beteiligt, ich weiß es nicht. Dann tauchte ein spezifischer Fall auf, und es wurde entschieden, dass sie trotz aller Widrigkeiten weiterhin eine direkte Änderung durch Aufrufen der Nicht-Konstanten-Methode ermöglichen. Dies ist jedoch eine Ausnahme. Sie dürfen temporäre Dateien im Allgemeinen nicht ändern. Ja, C ++ ist oft so komisch.
quelle
int
usw.) verboten , für benutzerdefinierte Typen jedoch zulässig :(std::string("A")+"B").append("C")
.g()
das Objekt ändern würde (was Sie von einer Funktion erwarten würden, die eine nicht konstante Referenz verwendet). es würde ein Objekt modifizieren, das sterben wird, so dass sowieso niemand den geänderten Wert erreichen könnte. Er sagt, dass dies höchstwahrscheinlich ein Fehler ist.In Ihrem Code wird
getx()
ein temporäres Objekt zurückgegeben, ein sogenannter "rvalue". Sie können r-Werte in Objekte (auch als Variablen bezeichnet) kopieren oder an konstante Referenzen binden (wodurch sich ihre Lebensdauer bis zum Ende der Lebensdauer der Referenz verlängert). Sie können r-Werte nicht an nicht konstante Referenzen binden.Dies war eine bewusste Entwurfsentscheidung, um zu verhindern, dass Benutzer versehentlich ein Objekt ändern, das am Ende des Ausdrucks sterben wird:
Wenn Sie dies tun möchten, müssen Sie entweder zuerst eine lokale Kopie oder das Objekt erstellen oder es an eine konstante Referenz binden:
Beachten Sie, dass der nächste C ++ - Standard rWertreferenzen enthält. Was Sie als Referenzen kennen, wird daher als "Wertreferenzen" bezeichnet. Sie können rvalues an rvalue-Referenzen binden und Funktionen für "rvalue-ness" überladen:
Die Idee hinter rvalue-Referenzen ist, dass Sie, da diese Objekte sowieso sterben werden, dieses Wissen nutzen und die sogenannte "Verschiebungssemantik" implementieren können, eine bestimmte Art der Optimierung:
quelle
g(getx())
funktioniert nicht, weil seine Signatur ein temporäres Objekt istg(X& x)
und esget(x)
zurückgibt, sodass wir ein temporäres Objekt ( rvalue ) nicht an eine nicht konstante Referenz binden können , richtig? Und in Ihrem erstenconst X& x2 = getx();
const X& x1 = getx();
:-/
Ja, Ihre Argumentation ist richtig, wenn auch etwas rückwärts: Wir können keine temporärenconst
Verweise an Nicht- (Wert-) Referenzen binden , und daher kann die vongetx()
(und nichtget(x)
) zurückgegebene temporäre Referenz nicht an die Wertreferenz gebunden werden, für die das Argument istg()
.getx()
(und nichtget(x)
) ?getx()
und nichtget(x)
(wie Sie geschrieben haben).Was Sie zeigen, ist, dass die Verkettung von Operatoren zulässig ist.
Der Ausdruck lautet 'getx (). Ref ();' und dies wird vor der Zuweisung zu 'x' vollständig ausgeführt.
Beachten Sie, dass getx () keine Referenz, sondern ein vollständig geformtes Objekt in den lokalen Kontext zurückgibt. Das Objekt ist temporär, aber nicht const, sodass Sie andere Methoden aufrufen können, um einen Wert zu berechnen, oder andere Nebenwirkungen auftreten können.
Schauen Sie sich das Ende dieser Antwort an, um ein besseres Beispiel dafür zu finden
Sie können eine temporäre Referenz nicht an eine Referenz binden, da dadurch eine Referenz auf ein Objekt generiert wird, das am Ende des Ausdrucks zerstört wird, sodass Sie eine baumelnde Referenz erhalten (die unordentlich ist und der Standard nicht unordentlich mag).
Der von ref () zurückgegebene Wert ist eine gültige Referenz, die Methode berücksichtigt jedoch nicht die Lebensdauer des zurückgegebenen Objekts (da diese Informationen nicht in ihrem Kontext enthalten sein können). Sie haben im Grunde nur das Äquivalent von getan:
Der Grund, warum es in Ordnung ist, dies mit einer konstanten Referenz auf ein temporäres Objekt zu tun, besteht darin, dass der Standard die Lebensdauer des temporären Objekts auf die Lebensdauer der Referenz verlängert, sodass die Lebensdauer der temporären Objekte über das Ende der Anweisung hinaus verlängert wird.
Die einzig verbleibende Frage ist also, warum der Standard nicht zulassen möchte, dass Verweise auf Provisorien die Lebensdauer des Objekts über das Ende der Aussage hinaus verlängern.
Ich glaube, das liegt daran, dass es dem Compiler sehr schwer fallen würde, temporäre Objekte zu korrigieren. Dies wurde für konstante Verweise auf temporäre Elemente durchgeführt, da dies nur eine begrenzte Verwendung hat und Sie daher gezwungen sind, eine Kopie des Objekts zu erstellen, um etwas Nützliches zu tun, das jedoch einige eingeschränkte Funktionen bietet.
Denken Sie an diese Situation:
Die Verlängerung der Lebensdauer dieses temporären Objekts wird sehr verwirrend sein.
Während der folgenden:
Gibt Ihnen Code, der intuitiv zu bedienen und zu verstehen ist.
Wenn Sie den Wert ändern möchten, sollten Sie den Wert an eine Variable zurückgeben. Wenn Sie versuchen, die Kosten für das Zurückkopieren des Objekts aus der Funktion zu vermeiden (da das Objekt anscheinend kopiert wurde (technisch gesehen)). Dann stören Sie sich nicht, der Compiler ist sehr gut in 'Rückgabewertoptimierung'
quelle
const_cast<x&>(getX());
macht keinen SinnWarum wird in den C ++ - FAQ ( fettgedruckte Mine) erläutert :
quelle
x * 0
gibtx
? Was? Was??incr(0);
. Wenn dies erlaubt wäre, würde es offensichtlich so interpretiert, dass eine temporäre Ganzzahl erstellt und anincr()
Das Hauptproblem ist das
ist ein logischer Fehler:
g
Ändert das Ergebnis von,getx()
aber Sie haben keine Chance, das geänderte Objekt zu untersuchen. Wenng
der Parameter nicht geändert werden müsste, wäre keine lvalue-Referenz erforderlich gewesen. Er hätte den Parameter nach Wert oder nach const-Referenz nehmen können.ist gültig, weil Sie manchmal das Ergebnis eines Ausdrucks wiederverwenden müssen und es ziemlich klar ist, dass Sie es mit einem temporären Objekt zu tun haben.
Es ist jedoch nicht möglich zu machen
gültig ohne gültig zu machen
g(getx())
, was die Sprachdesigner zunächst zu vermeiden versuchten.ist gültig, weil Methoden nur über die
this
Konstanz von Bescheid wissen und nicht wissen, ob sie für einen l-Wert oder einen r-Wert aufgerufen werden.Wie immer in C ++ haben Sie eine Problemumgehung für diese Regel, aber Sie müssen dem Compiler signalisieren, dass Sie wissen, was Sie tun, indem Sie explizit angeben:
quelle
Scheint wie die ursprüngliche Frage zu warum dies nicht zulässig ist, eindeutig beantwortet worden zu sein: "Weil es höchstwahrscheinlich ein Fehler ist".
FWIW, ich dachte ich würde es zeigen wie es geht, obwohl ich nicht denke, dass es eine gute Technik ist.
Der Grund, warum ich manchmal eine temporäre Methode an eine Methode übergeben möchte, die eine nicht konstante Referenz verwendet, besteht darin, absichtlich einen von der Referenz zurückgegebenen Wert wegzuwerfen, den die aufrufende Methode nicht interessiert. Etwas wie das:
Wie in den vorherigen Antworten erläutert, wird dies nicht kompiliert. Aber das kompiliert und funktioniert korrekt (mit meinem Compiler):
Dies zeigt nur, dass Sie Casting verwenden können, um den Compiler anzulügen. Offensichtlich wäre es viel sauberer, eine nicht verwendete automatische Variable zu deklarieren und zu übergeben:
Diese Technik führt eine nicht benötigte lokale Variable in den Anwendungsbereich der Methode ein. Wenn Sie aus irgendeinem Grund verhindern möchten, dass es später in der Methode verwendet wird, z. B. um Verwirrung oder Fehler zu vermeiden, können Sie es in einem lokalen Block ausblenden:
- Chris
quelle
Warum würdest du jemals wollen
X& x = getx();
? Verwenden Sie einfachX x = getx();
RVO und verlassen Sie sich darauf.quelle
g(getx())
lieber anrufen möchte alsg(getx().ref())
g
Sie etwas ändern werden, das Sie nicht mehr in die Hände bekommen können.Die böse Problemumgehung beinhaltet das Schlüsselwort "veränderlich". Das Böse zu sein, bleibt dem Leser als Übung überlassen. Oder sehen Sie hier: http://www.ddj.com/cpp/184403758
quelle
Ausgezeichnete Frage, und hier ist mein Versuch, eine präzisere Antwort zu finden (da viele nützliche Informationen in Kommentaren enthalten sind und sich im Lärm nur schwer ausgraben lassen.)
Jeder Verweis, der direkt an ein Temporär gebunden ist , verlängert dessen Lebensdauer [12.2.5]. Auf der anderen Seite wird eine mit einer anderen Referenz initialisierte Referenz nicht (auch wenn es sich letztendlich um dieselbe temporäre Referenz handelt ). Das ist sinnvoll (der Compiler weiß nicht, worauf sich diese Referenz letztendlich bezieht).
Aber diese ganze Idee ist äußerst verwirrend. ZB
const X &x = X();
wird das Temporäre so lange dauern wie diex
Referenz, aberconst X &x = X().ref();
NICHT (wer weiß, wasref()
tatsächlich zurückgegeben wurde). Im letzteren Fall wird der Destruktor fürX
am Ende dieser Zeile aufgerufen. (Dies ist mit einem nicht trivialen Destruktor zu beobachten.)Es scheint also im Allgemeinen verwirrend und gefährlich zu sein (warum die Regeln für die Lebensdauer von Objekten komplizieren?), Aber vermutlich waren zumindest konstante Referenzen erforderlich, sodass der Standard dieses Verhalten für sie festlegt.
Alle Provisorien bleiben bis zum Ende des vollständigen Ausdrucks bestehen. Um sie nutzen zu können, benötigen Sie jedoch einen Trick, mit dem Sie arbeiten
ref()
. Das ist legal. Es scheint keinen guten Grund zu geben, durch den zusätzlichen Rahmen zu springen, außer den Programmierer daran zu erinnern, dass etwas Ungewöhnliches vor sich geht (nämlich ein Referenzparameter, dessen Änderungen schnell verloren gehen).quelle
"Es ist klar, dass das temporäre Objekt im obigen Beispiel nicht konstant ist, da Aufrufe nicht konstanter Funktionen zulässig sind. Beispielsweise könnte ref () das temporäre Objekt ändern."
In Ihrem Beispiel gibt getX () keine const X zurück, sodass Sie ref () auf die gleiche Weise aufrufen können wie X (). Ref (). Sie geben eine Nicht-Konstanten-Referenz zurück und können daher Nicht-Konstanten-Methoden aufrufen. Sie können die Referenz jedoch nicht einer Nicht-Konstanten-Referenz zuweisen.
Zusammen mit dem SadSidos-Kommentar macht dies Ihre drei Punkte falsch.
quelle
Ich möchte ein Szenario mitteilen, in dem ich gerne tun könnte, was Alexey verlangt. In einem Maya C ++ - Plugin muss ich den folgenden Shenanigan ausführen, um einen Wert in ein Knotenattribut zu erhalten:
Das Schreiben ist mühsam, daher habe ich die folgenden Hilfsfunktionen geschrieben:
Und jetzt kann ich den Code vom Anfang des Beitrags schreiben als:
Das Problem ist, dass es nicht außerhalb von Visual C ++ kompiliert wird, da versucht wird, die vom | zurückgegebene temporäre Variable zu binden Operator zur MPlug-Referenz des Operators <<. Ich möchte, dass es eine Referenz ist, da dieser Code oft aufgerufen wird und ich lieber nicht möchte, dass MPlug so oft kopiert wird. Ich brauche nur das temporäre Objekt, um bis zum Ende der zweiten Funktion zu leben.
Nun, das ist mein Szenario. Ich dachte nur, ich würde ein Beispiel zeigen, wo man das tun möchte, was Alexey beschreibt. Ich freue mich über alle Kritiken und Vorschläge!
Vielen Dank.
quelle