wake-up-neo.com

Wie kommt es, dass eine Nicht-Konstanten-Referenz nicht an ein temporäres Objekt gebunden werden kann?

Warum ist es nicht erlaubt, einen nicht konstanten Verweis auf ein temporäres Objekt zu erhalten, was die Funktion getx() zurückgibt? Natürlich ist dies durch den C++ Standard verboten, aber ich bin an dem Zweck einer solchen Einschränkung interessiert, kein 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;
}
  1. Es ist klar, dass die Lebensdauer des Objekts nicht die Ursache sein kann, da ein ständiger Verweis auf ein Objekt nach C++ Standard nicht verboten ist .
  2. Es ist klar, dass das temporäre Objekt im obigen Beispiel nicht konstant ist, da Aufrufe von nicht konstanten Funktionen zulässig sind. Beispielsweise könnte ref() das temporäre Objekt ändern.
  3. Darüber hinaus können Sie mit ref() den Compiler zum Narren halten und eine Verknüpfung zu diesem temporären Objekt erstellen, wodurch unser Problem behoben wird.

Zusätzlich:

Sie sagen, dass "das Zuweisen eines temporären Objekts zu der const-Referenz die Lebensdauer dieses Objekts verlängert" und "obwohl nichts über nicht const-Referenzen gesagt wird". Mein zusätzliche Frage. Verlängert die folgende Zuordnung die Lebensdauer des temporären Objekts?

X& x = getx().ref(); // OK
218
Alexey Malistov

In Ihrem Code gibt getx() ein temporäres Objekt zurück, einen sogenannten "rvalue". Sie können Werte in Objekte (auch als Variablen bezeichnet) kopieren oder sie an konstante Referenzen binden (wodurch ihre Lebensdauer bis zum Ende der Lebensdauer der Referenz verlängert wird). Sie können keine r-Werte an nicht konstante Referenzen binden.

Dies war eine absichtliche Entwurfsentscheidung, um zu verhindern, dass Benutzer versehentlich ein Objekt ändern, das am Ende des Ausdrucks abstirbt:

g(getx()); // g() would modify an object without anyone being able to observe

Wenn Sie dies tun möchten, müssen Sie entweder zuerst eine lokale Kopie oder des Objekts erstellen oder es an eine const-Referenz binden:

X x1 = getx();
const X& x2 = getx(); // extend lifetime of temporary to lifetime of const reference

g(x1); // fine
g(x2); // can't bind a const reference to a non-const reference

Beachten Sie, dass der nächste C++ - Standard rWertreferenzen enthält. Was Sie als Referenzen kennen, wird daher zu "Wertereferenzen". Sie können rWerte an rWertreferenzen binden und Funktionen für "rWert-ness" überladen:

void g(X&);   // #1, takes an ordinary (lvalue) reference
void g(X&&);  // #2, takes an rvalue reference

X x; 
g(x);      // calls #1
g(getx()); // calls #2
g(X());    // calls #2, too

Die Idee hinter rvalue-Referenzen ist, dass Sie, da diese Objekte sowieso sterben werden, dieses Wissen nutzen und die sogenannte "Bewegungssemantik" implementieren können, eine bestimmte Art von Optimierung:

class X {
  X(X&& rhs)
    : pimpl( rhs.pimpl ) // steal rhs' data...
  {
    rhs.pimpl = NULL; // ...and leave it empty, but deconstructible
  }

  data* pimpl; // you would use a smart ptr, of course
};


X x(getx()); // x will steal the rvalue's data, leaving the temporary object empty
34
sbi

Sie zeigen, dass das Verketten von Operatoren zulässig ist.

 X& x = getx().ref(); // OK

Der Ausdruck lautet 'getx (). Ref ();' und dies wird vollständig ausgeführt, bevor die Zuordnung zu 'x' erfolgt.

Beachten Sie, dass getx () keine Referenz, sondern ein vollständig geformtes Objekt in den lokalen Kontext zurückgibt. Das Objekt ist temporär, aber es ist keine Konstante, sodass Sie andere Methoden aufrufen können, um einen Wert zu berechnen, oder andere Nebenwirkungen auftreten.

// It would allow things like this.
getPipeline().procInstr(1).procInstr(2).procInstr(3);

// or more commonly
std::cout << getManiplator() << 5;

Ein besseres Beispiel hierfür finden Sie am Ende dieser Antwort.

Sie können ein temporäres Objekt nicht an einen Verweis binden , da dadurch ein Verweis auf ein Objekt generiert wird, das am Ende des Ausdrucks zerstört wird und Sie somit verlassen mit einem baumelnden Verweis (der unordentlich ist und der Standard nicht gerne unordentlich ist).

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 im Kontext enthalten sein können). Sie haben im Grunde nur das Äquivalent von:

x& = const_cast<x&>(getX());

Der Grund, warum dies mit einem konstanten Verweis auf ein temporäres Objekt in Ordnung ist, ist, dass der Standard die Lebensdauer des temporären Objekts auf die Lebensdauer des Verweises verlängert, sodass die Lebensdauer des temporären Objekts über das Ende der Anweisung hinaus verlängert wird.

Die einzige verbleibende Frage ist also, warum die Norm nicht zulassen möchte, dass Verweise auf Temporäre die Lebensdauer des Objekts über das Ende der Anweisung hinaus verlängern.

Ich glaube, es ist, weil dies den Compiler sehr schwer machen würde, für temporäre Objekte richtig zu werden. Es wurde für const-Verweise auf temporäre Dateien durchgeführt, da dies nur eine begrenzte Verwendung hat und Sie daher gezwungen sind, eine Kopie des Objekts zu erstellen, um alles Nützliche zu tun, bietet jedoch einige eingeschränkte Funktionen.

Denken Sie an diese Situation:

int getI() { return 5;}
int x& = getI();

x++; // Note x is an alias to a variable. What variable are you updating.

Die Verlängerung der Lebensdauer dieses temporären Objekts wird sehr verwirrend sein.
Während der folgenden:

int const& y = getI();

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 kopierkonstruiert ist (technisch gesehen)). Dann kümmere dich nicht darum, dass der Compiler gut in 'Rückgabewertoptimierung' ist

16
Martin York

Warum wird in der C++ FAQ ( fettgedruckten Mine besprochen ):

In C++ können Nicht-Konstanten-Referenzen an I-Werte und Konstanten-Referenzen an I-Werte oder R-Werte gebunden werden, aber es gibt nichts, was an einen Nicht-Konstanten-R-Wert gebunden werden kann. Dies ist , um Menschen davor zu schützen, die Werte von Provisorien zu ändern, die zerstört werden, bevor ihr neuer Wert verwendet werden kann . Beispielsweise:

void incr(int& a) { ++a; }
int i = 0;
incr(i);    // i becomes 1
incr(0);    // error: 0 is not an lvalue

Wenn dieses incr (0) erlaubt wäre, würde entweder ein temporäres, das niemand jemals gesehen hat, erhöht, oder - weitaus schlimmer - der Wert von 0 würde 1 werden. Letzteres klingt albern, aber es gab tatsächlich einen solchen Fehler in den frühen Fortran-Compilern, die eingestellt wurden Neben einem Speicherplatz für den Wert 0.

9
Tony Delroy

Das Hauptproblem ist das

g(getx()); //error

ist ein logischer Fehler: g ändert das Ergebnis von getx(), aber Sie haben keine Chance, das geänderte Objekt zu untersuchen. Wenn g seinen Parameter nicht ändern müsste, dann hätte es keine l-Wert-Referenz benötigt, es könnte den Parameter nach Wert oder nach const-Referenz genommen haben.

const X& x = getx(); // OK

ist gültig, weil Sie das Ergebnis eines Ausdrucks manchmal wiederverwenden müssen und es ziemlich klar ist, dass es sich um ein temporäres Objekt handelt.

Es ist jedoch nicht möglich zu machen

X& x = getx(); // error

gültig, ohne g(getx()) gültig zu machen, was die Sprachentwickler zuallererst zu vermeiden versuchten.

g(getx().ref()); //OK

ist gültig, weil Methoden nur über die Konstanz des this 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:

g(const_cast<x&>(getX()));
6
Dan Berindei

Wie es scheint, wurde die ursprüngliche Frage nach warum dies nicht erlaubt eindeutig beantwortet: "weil es höchstwahrscheinlich ein Fehler ist".

FWIW, ich dachte, ich würde zeigen wie, um es zu tun, obwohl ich nicht denke, dass es eine gute Technik ist.

Der Grund, warum ich manchmal eine temporäre Variable an eine Methode übergeben möchte, die eine nicht konstante Referenz verwendet, besteht darin, absichtlich einen als Referenz zurückgegebenen Wert zu verwerfen, den die aufrufende Methode nicht berücksichtigt. Etwas wie das:

// Assuming: void Person::GetNameAndAddr(std::string &name, std::string &addr);
string name;
person.GetNameAndAddr(name, string()); // don't care about addr

Wie in den vorherigen Antworten erläutert, wird dies nicht kompiliert. Aber dies kompiliert und funktioniert korrekt (mit meinem Compiler):

person.GetNameAndAddr(name,
    const_cast<string &>(static_cast<const string &>(string())));

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:

string name;
string unused;
person.GetNameAndAddr(name, unused); // don't care about addr

Diese Technik führt eine nicht benötigte lokale Variable in den Bereich 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:

string name;
{
    string unused;
    person.GetNameAndAddr(name, unused); // don't care about addr
}

- Chris

5
Chris Pearson

Die böse Problemumgehung beinhaltet das Schlüsselwort 'mutable'. Das Böse zu sein, ist eine Übung für den Leser. Oder sehen Sie hier: http://www.ddj.com/cpp/184403758

4
paul

Warum sollten Sie jemals X& x = getx(); wollen? Verwenden Sie einfach X x = getx(); und verlassen Sie sich auf RVO.

4
DevFred

Ausgezeichnete Frage, und hier ist mein Versuch, eine präzisere Antwort zu finden (da viele nützliche Informationen in Kommentaren enthalten sind und sich nur schwer im Rauschen ausgraben lassen.)

Jede Referenz, die direkt an ein temporäres Objekt gebunden ist , verlängert dessen Lebensdauer [12.2.5]. Auf der anderen Seite wird eine Referenz, die mit einer anderen Referenz initialisiert wurde, nicht (auch wenn es letztendlich dieselbe temporäre ist). Das ist sinnvoll (der Compiler weiß nicht, worauf sich diese Referenz letztendlich bezieht).

Aber diese ganze Idee ist äußerst verwirrend. Z.B. const X &x = X(); macht die temporäre Ausgabe so lange wie die x Referenz, aber const X &x = X().ref(); NICHT (wer weiß, welche ref() tatsächlich zurückgegeben hat). Im letzteren Fall wird der Destruktor für X 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 zur Lebensdauer von Objekten komplizieren?), Aber vermutlich waren zumindest const-Referenzen erforderlich, sodass der Standard dieses Verhalten für sie festlegt.

[From sbi comment]: Beachten Sie, dass die Tatsache, dass das Binden an eine const-Referenz die Lebensdauer einer temporären Instanz verlängert, eine bewusst hinzugefügte Ausnahme ist (TTBOMK, um manuelle Optimierungen zu ermöglichen). Es wurde keine Ausnahme für Nicht-Konstanten-Verweise hinzugefügt, da das Binden eines temporären Verweises an einen Nicht-Konstanten-Verweis höchstwahrscheinlich ein Programmiererfehler war.

Alle Provisorien bleiben bis zum Ende des vollständigen Ausdrucks bestehen. Um sie zu nutzen, benötigen Sie jedoch einen Trick, wie Sie ihn mit ref() haben. Das ist legal. Es scheint keinen guten Grund für das Durchspringen des zusätzlichen Rahmens zu geben, außer um den Programmierer daran zu erinnern, dass etwas Ungewöhnliches passiert (nämlich ein Referenzparameter, dessen Änderungen schnell verloren gehen).

[Ein weiterer sbi Kommentar] Der Grund, warum Stroustrup (in D & E) angibt, dass die Bindung von Werten an nicht konstante Referenzen nicht zulässig ist, ist, dass Alexeys g() geändert würde Das Objekt (was man von einer Funktion erwarten würde, die eine Nicht-Konstanten-Referenz verwendet) würde ein Objekt modifizieren, das sterben wird, so dass sowieso niemand den geänderten Wert erreichen kann .

3
DS.

"Es ist klar, dass das temporäre Objekt im obigen Beispiel nicht konstant ist, da Aufrufe von nicht konstanten Funktionen zulässig sind. Beispielsweise könnte ref () das temporäre Objekt ändern."

In Ihrem Beispiel gibt getX () keine Konstante X zurück, sodass Sie ref () auf die gleiche Weise aufrufen können wie X (). Ref (). Sie geben einen Nicht-Konstanten-Verweis zurück und können daher Nicht-Konstanten-Methoden aufrufen. Sie können den Verweis jedoch nicht einer Nicht-Konstanten-Referenz zuweisen.

Zusammen mit SadSidos Kommentar macht dies Ihre drei Punkte falsch.

2
Patrick

Ich möchte ein Szenario vorstellen, in dem ich wünschte, ich könnte das tun, was Alexey verlangt. In einem Maya C++ Plugin muss ich die folgende Aktion ausführen, um einen Wert in ein Knotenattribut zu bekommen:

MFnDoubleArrayData myArrayData;
MObject myArrayObj = myArrayData.create(myArray);   
MPlug myPlug = myNode.findPlug(attributeName);
myPlug.setValue(myArrayObj);

Das ist mühsam zu schreiben, deshalb habe ich die folgenden Hilfsfunktionen geschrieben:

MPlug operator | (MFnDependencyNode& node, MObject& attribute){
    MStatus status;
    MPlug returnValue = node.findPlug(attribute, &status);
    return returnValue;
}

void operator << (MPlug& plug, MDoubleArray& doubleArray){
    MStatus status;
    MFnDoubleArrayData doubleArrayData;
    MObject doubleArrayObject = doubleArrayData.create(doubleArray, &status);
    status = plug.setValue(doubleArrayObject);
}

Und jetzt kann ich den Code vom Anfang des Beitrags schreiben als:

(myNode | attributeName) << myArray;

Das Problem ist, dass es nicht außerhalb von Visual C++ kompiliert wird, da versucht wird, die temporäre Variable zu binden, die von der | zurückgegeben wird Operator auf die MPlug-Referenz des << Operators. Ich möchte, dass es eine Referenz ist, da dieser Code oft aufgerufen wird und MPlug nicht so oft kopiert werden soll. 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 machen möchte, was Alexey beschreibt. Ich begrüße alle Kritik und Vorschläge!

Vielen Dank.

1