2009-06-24 17 views
14

Wenn ich kompilieren den folgenden Code g++Referenz-Konvertierungsoperatoren: Fragen nach Ärger?

class A {}; 

void foo(A&) {} 

int main() 
{ 
    foo(A()); 
    return 0; 
} 

Ich bekomme die folgende Fehlermeldung mit:

> g++ test.cpp -o test  
test.cpp: In function ‘int main()’: 
test.cpp:10: error: invalid initialization of non-const reference of type ‘A&’ from a temporary of type ‘A’ 
test.cpp:6: error: in passing argument 1 of ‘void foo(A&)’ 

Nach einigem Nachdenken, diese Fehler viel Sinn für mich. A() ist nur ein temporärer Wert, kein zuweisbarer Speicherort auf dem Stapel, daher scheint es keine Adresse zu haben. Wenn es keine Adresse hat, kann ich keinen Hinweis darauf halten. Okay gut.

Aber warte! Wenn ich den folgenden Konvertierungsoperator die Klasse A

class A 
{ 
public: 
    operator A&() { return *this; } 
}; 
hinzufügen

dann ist alles gut! Meine Frage ist, ob dies auch nur annähernd sicher ist. Was genau zeigt this auf, wenn A() als temporärer Wert erstellt wird?

Ich bin etwas Vertrauen durch die Tatsache gegeben, dass

void foo(const A&) {} 

temporäre Werte nach g++ und alle anderen Compilern akzeptieren kann ich benutzt habe. Das const Schlüsselwort kann immer weggeworfen werden, so würde es mich überraschen, wenn es irgendwelche tatsächlichen semantischen Unterschiede zwischen einem const A& Parameter und einem A& Parameter gäbe. Also denke ich, dass das eine andere Art ist, meine Frage zu stellen: Warum ist ein const Verweis auf einen temporären Wert, der vom Compiler als sicher betrachtet wird, während ein nicht-const Verweis nicht?

Antwort

16

Es ist nicht so, dass eine Adresse nicht genommen werden kann (der Compiler könnte sie immer auf den Stapel schieben, was er mit ref-to-const tut), es ist eine Frage der Absicht des Programmierers. Mit einer Schnittstelle, die ein A & nimmt, sagt es "ich werde ändern, was in diesem Parameter ist, also können Sie nach dem Funktionsaufruf lesen". Wenn Sie es vorübergehend übergeben, dann existiert das Ding, das es "modifiziert" hat, nicht nach der Funktion. Dies ist (wahrscheinlich) ein Programmierfehler, daher ist es nicht erlaubt. Betrachten wir zum Beispiel:

void plus_one(int & x) { ++x; } 

int main() { 
    int x = 2; 
    float f = 10.0; 

    plus_one(x); plus_one(f); 

    cout << x << endl << f << endl; 
} 

Dies gilt nicht kompilieren, aber wenn Provisorien zu einem ref-zu-nicht-const binden könnte, wäre es kompilieren, aber überraschende Ergebnisse. In plus_one (f) würde f implizit in ein temporäres int konvertiert, plus_one würde das temp nehmen und inkrementieren, wobei das zugrunde liegende float f unberührt bleibt. Wenn plus_one zurückgegeben wurde, hätte es keinen Effekt gehabt. Dies ist mit ziemlicher Sicherheit nicht das, was der Programmierer beabsichtigt hat.


Die Regel macht gelegentlich Fehler. Ein gängiges Beispiel (beschrieben here) versucht, eine Datei zu öffnen, etwas zu drucken und es zu schließen.Sie würden wollen in der Lage sein zu tun:

ofstream("bar.t") << "flah"; 

Aber man kann nicht, weil Betreiber < < nimmt eine ref-zu-nicht-const. Ihre Optionen in zwei Zeilen umbrochen werden, oder ein Verfahren, rufen Sie eine ref-zu-nicht-const Rückkehr:

ofstream("bar.t").flush() << "flah"; 
+0

Ich stimme zu, dass diese Art von Sache normalerweise ein Programmierfehler ist. Für den Fall, dass Sie neugierig sind, ist die Klasse, die ich als Referenz übergeben möchte, eine Art intelligenter Zeiger, dem ein zugrunde liegendes Heap zugewiesenes Zeigerelement zugeordnet ist. Ich möchte tatsächlich, dass die Empfangsfunktion Dinge mit dem zugrunde liegenden Zeiger ausführt. Ich möchte nur sicher sein, dass der temporäre Smart-Zeiger nicht zerstört wird (Dekrementieren der Referenzzählung), bis die Empfangsfunktion zurückkehrt. – Ben

+1

Wenn dies Ihre Absicht ist, deklarieren Sie entweder den Smart Pointer explizit auf dem Stack, übergeben ihn oder übergeben ihn wertmäßig. Wenn Sie sie als Wert übergeben, ist sichergestellt, dass sie nicht zerstört wird, bis die Funktion beendet wird. – MSN

+0

@Ben - hinzugefügt einige zu meinem Beitrag darüber. Kann nicht sagen, ob es eine gute Idee für Sie ist, ohne Ihren Code zu sehen, aber es gibt einige Anwendungsfälle (und es wird nicht zerstört, bis es zurückkehrt) –

4

Wenn Sie einem const-Verweis einen r-Wert zuweisen, wird garantiert, dass das temporäre Objekt erst zerstört wird, wenn die Referenz zerstört wurde. Wenn Sie einer nichtkonformen Referenz zuweisen, wird keine solche Garantie gewährt.

int main() 
{ 
    const A& a2= A(); // this is fine, and the temporary will last until the end of the current scope. 
    A& a1 = A(); // You can't do this. 
} 

Sie können const-ness nicht sicher wegwerfen und erwarten, dass die Dinge funktionieren. Es gibt verschiedene Semantiken für konstante und nicht-konstante Referenzen.

+0

Im Allgemeinen wird ein temporäres Objekt am Ende der Auswertung des Ausdrucks, der es erstellt hat, zerstört. Es gibt zwei Ausnahmen, wenn sich die Lebensdauer eines temporären Objekts verlängert, auf eines davon wird von @Eclipse hingewiesen. Siehe: http://en.cppreference.com/w/cpp/language/lifetime#Temporary_object_lifetime – winnetou

3

Gotcha, dass einige Leute in ausführen können: die MSVC-Compiler (Visual Studio Compiler, verifiziert mit Visual Studio 2008) Willen kompilieren Sie diesen Code ohne Probleme. Wir hatten dieses Paradigma in einem Projekt für Funktionen verwendet, die normalerweise ein Argument (einen Datenblock zum Verdauen) benötigten, aber manchmal den Block durchsuchen und die Ergebnisse dem Aufrufer zurückgeben wollten. Der andere Modus wurde aktiviert, indem drei Argumente verwendet wurden - das zweite Argument war die zu durchsuchende Information (Standardreferenz auf eine leere Zeichenfolge) und das dritte Argument war für die Rückgabedaten (Standardreferenz auf die leere Liste des gewünschten Typs).

Dieses Paradigma funktionierte in Visual Studio 2005 und 2008, und wir mussten es umgestalten, so dass die Liste erstellt und zurückgegeben wurde anstelle von im Besitz von Aufrufer und mutiert, um mit g ++ zu kompilieren.

Wenn es eine Möglichkeit gibt, die Compiler-Schalter zu setzen, um diese Art von Verhalten in MSVC entweder zuzulassen oder in g ++ zuzulassen, würde ich mich freuen zu wissen; die Zulässigkeit des MSVC-Compilers/Restriktivität des g ++ - Compilers fügt dem Portierungscode Komplikationen hinzu.