Verlängert ein Mitglied einer konstanten Referenzklasse die Lebensdauer eines temporären?

171

Warum macht das:

#include <string>
#include <iostream>
using namespace std;

class Sandbox
{
public:
    Sandbox(const string& n) : member(n) {}
    const string& member;
};

int main()
{
    Sandbox sandbox(string("four"));
    cout << "The answer is: " << sandbox.member << endl;
    return 0;
}

Geben Sie die Ausgabe von:

Die Antwort ist:

Anstatt:

Die Antwort lautet: vier

Kyle
quelle
39
Und nur für mehr Spaß, wenn Sie geschrieben hätten cout << "The answer is: " << Sandbox(string("four")).member << endl;, dann würde es garantiert funktionieren.
7
@ RogerPate Kannst du erklären warum?
Paolo M
16
Für jemanden, der neugierig ist, funktioniert das Beispiel, das Roger Pate gepostet hat, weil die Zeichenfolge ("vier") temporär ist und diese temporäre Zeichenfolge am Ende des vollständigen Ausdrucks zerstört wird. In seinem Beispiel SandBox::memberist die temporäre Zeichenfolge also noch lebendig, wenn sie gelesen wird .
PcAF
1
Die Frage ist: Da das Schreiben solcher Klassen gefährlich ist, gibt es eine Compiler-Warnung vor dem Übergeben von Provisorien an solche Klassen oder gibt es eine Entwurfsrichtlinie (in Stroustroup?), Die das Schreiben von Klassen zum Speichern von Referenzen verbietet? Eine Entwurfsrichtlinie zum Speichern von Zeigern anstelle von Referenzen wäre besser.
Grim Fandango
@PcAF: Könnten Sie bitte erklären, warum das temporäre Element string("four")am Ende des vollständigen Ausdrucks und nicht nach dem Beenden des SandboxKonstruktors zerstört wird? Die Antwort von Potatoswatter lautet: Eine temporäre Bindung an ein Referenzelement im ctor-Initialisierer eines Konstruktors (§12.6.2 [class.base.init]) bleibt bestehen, bis der Konstruktor beendet wird.
Taylor Nichols

Antworten:

166

Nur lokale const Referenzen verlängern die Lebensdauer.

Der Standard spezifiziert ein solches Verhalten in §8.5.3 / 5, [dcl.init.ref], dem Abschnitt über Initialisierer von Referenzdeklarationen. Die Referenz in Ihrem Beispiel ist an das Argument des Konstruktors gebunden nund wird ungültig, wenn das Objekt naußerhalb des Gültigkeitsbereichs liegt.

Die Lebensdauerverlängerung ist nicht durch ein Funktionsargument transitiv. §12.2 / 5 [class.temporary]:

Der zweite Kontext ist, wenn eine Referenz an eine temporäre Referenz gebunden ist. Das temporäre Objekt, an das die Referenz gebunden ist, oder das temporäre Objekt, das das vollständige Objekt eines Unterobjekts ist, an das das temporäre Objekt gebunden ist, bleibt für die Lebensdauer der Referenz bestehen, sofern nachstehend nichts anderes angegeben ist. Eine temporäre Bindung an ein Referenzelement im ctor-Initialisierer eines Konstruktors (§12.6.2 [class.base.init]) bleibt bestehen, bis der Konstruktor beendet wird. Eine temporäre Bindung an einen Referenzparameter in einem Funktionsaufruf (§5.2.2 [expr.call]) bleibt bis zum Abschluss des vollständigen Ausdrucks bestehen, der den Aufruf enthält.

Kartoffelklatsche
quelle
49
Sie sollten auch GotW # 88 für eine menschlichere Erklärung sehen: herbsutter.com/2008/01/01/…
Nathan Ernst
1
Ich denke, es wäre klarer, wenn der Standard sagen würde: "Der zweite Kontext ist, wenn eine Referenz an einen Wert gebunden ist." Im OP-Code könnte man sagen, dass dies memberan ein temporäres Objekt gebunden ist , da das Initialisieren membermit nMitteln zum Binden memberan dasselbe Objekt nan ein temporäres Objekt gebunden ist und dies in diesem Fall tatsächlich ein temporäres Objekt ist.
MM
2
@MM Es gibt Fälle, in denen lvalue- oder xvalue-Initialisierer, die einen prvalue enthalten, den prvalue erweitern. Mein Vorschlagspapier P0066 prüft den Stand der Dinge.
Potatoswatter
1
Ab C ++ 11 verlängern Rvalue-Referenzen auch die Lebensdauer eines temporären constSystems, ohne dass ein Quaifier erforderlich ist.
GetFree
3
@ KeNVinFavo ja, mit einem toten Objekt ist immer UB
Potatoswatter
30

Hier ist der einfachste Weg, um zu erklären, was passiert ist:

In main () haben Sie eine Zeichenfolge erstellt und an den Konstruktor übergeben. Diese Zeichenfolgeninstanz war nur im Konstruktor vorhanden. Innerhalb des Konstruktors haben Sie ein Mitglied zugewiesen, das direkt auf diese Instanz verweist. Als der Bereich den Konstruktor verließ, wurde die Zeichenfolgeninstanz zerstört, und das Mitglied zeigte auf ein Zeichenfolgenobjekt, das nicht mehr vorhanden war. Wenn Sandbox.member auf eine Referenz außerhalb seines Gültigkeitsbereichs verweist, werden diese externen Instanzen nicht im Gültigkeitsbereich gespeichert.

Wenn Sie Ihr Programm so reparieren möchten, dass das gewünschte Verhalten angezeigt wird, nehmen Sie die folgenden Änderungen vor:

int main()
{
    string temp = string("four");    
    Sandbox sandbox(temp);
    cout << sandbox.member << endl;
    return 0;
}

Jetzt wird temp am Ende von main () anstatt am Ende des Konstruktors nicht mehr gültig sein. Dies ist jedoch eine schlechte Praxis. Ihre Mitgliedsvariable sollte niemals ein Verweis auf eine Variable sein, die außerhalb der Instanz existiert. In der Praxis wissen Sie nie, wann diese Variable den Gültigkeitsbereich verlässt.

Ich empfehle, Sandbox.member als zu definieren. const string member;Dadurch werden die Daten des temporären Parameters in die Membervariable kopiert, anstatt die Membervariable als temporären Parameter selbst zuzuweisen .

Squirrelsama
quelle
Wenn ich das mache: const string & temp = string("four"); Sandbox sandbox(temp); cout << sandbox.member << endl;Wird es noch funktionieren?
Yves
@Thomas const string &temp = string("four");gibt das gleiche Ergebnis wie const string temp("four"); , wenn Sie nicht decltype(temp)speziell verwenden
MM
@MM Vielen Dank, jetzt verstehe ich diese Frage total.
Yves
However, this is bad practice.- Warum? Wenn sowohl das temporäre als auch das enthaltende Objekt automatische Speicherung im selben Bereich verwenden, ist es nicht 100% sicher? Und wenn Sie das nicht tun, was würden Sie tun, wenn die Zeichenfolge zu groß und zu teuer zum Kopieren ist?
Max
2
@max, da die Klasse die übergebene temporäre Datei nicht erzwingt, um den richtigen Gültigkeitsbereich zu erhalten. Dies bedeutet, dass Sie eines Tages möglicherweise diese Anforderung vergessen haben, einen ungültigen temporären Wert übergeben und der Compiler Sie nicht warnt.
Alex Che
5

Technisch gesehen ist dieses Programm nicht erforderlich, um tatsächlich etwas an die Standardausgabe auszugeben (dies ist zunächst ein gepufferter Stream).

  • Das cout << "The answer is: "Bit wird "The answer is: "in den Puffer von stdout ausgegeben.

  • Dann << sandbox.memberliefert das Bit die baumelnde Referenz in operator << (ostream &, const std::string &), die undefiniertes Verhalten hervorruft .

Aus diesem Grund ist garantiert nichts passiert. Das Programm funktioniert möglicherweise einwandfrei oder stürzt ab, ohne dass stdout gelöscht wird. Dies bedeutet, dass der Text "Die Antwort lautet:" nicht auf Ihrem Bildschirm angezeigt wird.

Tanz87
quelle
2
Wenn es UB gibt, ist das Verhalten des gesamten Programms undefiniert - es beginnt nicht nur an einem bestimmten Punkt in der Ausführung. Wir können also nicht mit Sicherheit sagen, dass "The answer is: "das irgendwo geschrieben wird.
Toby Speight
0

Da Ihre temporäre Zeichenfolge nach der Rückkehr des Sandbox-Konstruktors nicht mehr gültig war und der von ihm belegte Stapel für andere Zwecke zurückgefordert wurde.

Im Allgemeinen sollten Sie Referenzen niemals langfristig aufbewahren. Referenzen sind gut für Argumente oder lokale Variablen, niemals für Klassenmitglieder.

Fjodor Soikin
quelle
7
"Niemals" ist ein schrecklich starkes Wort.
Fred Larson
17
Niemals Klassenmitglieder, es sei denn, Sie müssen einen Verweis auf ein Objekt behalten. Es gibt Fälle, in denen Sie Verweise auf andere Objekte und keine Kopien aufbewahren müssen. In diesen Fällen sind Verweise eine klarere Lösung als Zeiger.
David Rodríguez - Dribeas
0

Sie beziehen sich auf etwas, das verschwunden ist. Folgendes wird funktionieren

#include <string>
#include <iostream>

class Sandbox
{

public:
    const string member = " "; //default to whatever is the requirement
    Sandbox(const string& n) : member(n) {}//a copy is made

};

int main()
{
    Sandbox sandbox(string("four"));
    std::cout << "The answer is: " << sandbox.member << std::endl;
    return 0;
}
pcodex
quelle