Immer konstante Referenz als Parameter
-
Simon2 schrieb:
hustbaer schrieb:
const-ref als Return-Typ ist ein heikles Thema....
Gibt's dazu eigentlich eine gute "Übersicht" ? Ich muss gestehen, dass ich schon diverse Erläuterungen von camper gelesen ..... und nicht verstanden habe.
Ich kann mich nicht erinnern, bisher besonders auf Referenzrückgaben eingegangen zu sein. Das ist schon deshalb nicht besonders geboten, weil ich hier nicht grundsätzlich von den Empfehlungen aus Effektiv C++&Co. abweichen würde.
Immerhin meine ich zu wissen, dass eine const-ref auf eine temporäre Variable wenigstens "den umschließenden Ausdruck überlebt" und deswegen sowas funktioniert:
Ein temporäres Objekt, dass an eine Rückgabereferenz gebunden wird, wird bei Verlassen der Funktion zerstört (damit lebt es immer noch länger, als es ohne diese Bindung gelebt hätte). Betrachte
#include <iostream> using namespace std; struct Foo { const char* s; Foo(const char* s) : s(s) { cout << s << endl; } Foo& self() { return *this; } ~Foo() { cout << '~' << s << endl; } }; const Foo& f() { Foo a("a"); return Foo("b").self(); // Das return-Argument ist kein temporäres Objekt } const Foo& g() { Foo c("c"); return Foo("d"); // return-Argument ist temporäres Objekt } int main() { f(); cout << "***\n"; g(); }
Ausgabe (ungetestet):
a b ~b ~a *** c d ~c ~d
-
camper schrieb:
Simon2 schrieb:
hustbaer schrieb:
const-ref als Return-Typ ist ein heikles Thema....
Gibt's dazu eigentlich eine gute "Übersicht" ? Ich muss gestehen, dass ich schon diverse Erläuterungen von camper gelesen ..... und nicht verstanden habe.
Ich kann mich nicht erinnern, bisher besonders auf Referenzrückgaben eingegangen zu sein. ...
Naja, das war oft im Zusammenhang mit RVO-Diskussionen - aber egal.
camper schrieb:
Ein temporäres Objekt, dass an eine Rückgabereferenz gebunden wird, wird bei Verlassen der Funktion zerstört (damit lebt es immer noch länger, als es ohne diese Bindung gelebt hätte)
Ich hatte in Erinnerung, dass da const& ein wenig von non-const& abweichen würden ...
Dein Code liefert bei mir (IBM XL C/C++ V.8.0 mit Defaultoptionen) allerdings:
a b ~b ~a *** c d ~d ~c
Ergo: Kein Unterschied.
Gruß,
Simon2.
-
camper schrieb:
Ein temporäres Objekt, dass an eine Rückgabereferenz gebunden wird, wird bei Verlassen der Funktion zerstört (damit lebt es immer noch länger, als es ohne diese Bindung gelebt hätte).
Das musst du mir jetzt aber mal erklären - auch aus deinem Code werde ich nicht so richtig schlau. Könntest du das auch mit den entsprechenden Stellen aus dem Standard belegen?
-
Ich hab das Ganze noch ein wenig modifiziert
#include <iostream> using namespace std; struct Foo { const char* s; Foo(const char* s) : s(s) { cout << s << " "; } Foo& self() { return *this; } ~Foo() { cout << '~' << s << " "; } }; const Foo& f() { return Foo("a"), Foo("b").self(); } const Foo& g() { return Foo("c"), Foo("d"); } int main() { f(); cout << '\n'; g(); cout << '\n'; }
Getestet mit g++-Versionen 3.3.6, 3.4.6, 4.1.2, 4.2.4, 4.3.2 ergibt jeweils
a b ~b ~a c d ~d ~c
Der Standard ist hier imo widersprüchlich, also kein Bug (ich nehme meine Behauptung also zurück)
C++03 schrieb:
12.2/5
1The second context is when a reference is bound to a temporary. 2The temporary to which the reference is
bound or the temporary that is the complete object to a subobject of which the temporary is bound persists
for the lifetime of the reference except as specified below. 3A temporary bound to a reference member in a
constructor’s ctor-initializer (12.6.2) persists until the constructor exits. 4A temporary bound to a reference
parameter in a function call (5.2.2) persists until the completion of the full expression containing the call.
5A temporary bound to the returned value in a function return statement (6.6.3) persists until the function
exits. 6In all these cases, the temporaries created during the evaluation of the expression initializing the reference,
except the temporary to which the reference is bound, are destroyed at the end of the full-expression
in which they are created and in the reverse order of the completion of their construction. 7If the lifetime of
two or more temporaries to which references are bound ends at the same point, these temporaries are
destroyed at that point in the reverse order of the completion of their construction. 8In addition, the
destruction of temporaries bound to references shall take into account the ordering of destruction of objects
with static or automatic storage duration (3.7.1, 3.7.2); that is, if obj1 is an object with static or automatic
storage duration created before the temporary is created, the temporary shall be destroyed before obj1 is
destroyed; if obj2 is an object with static or automatic storage duration created after the temporary is created,
the temporary shall be destroyed after obj2 is destroyed.Meiner Ansicht nach besteht ein direkter Widerspruch zwischen den Sätzen 6 und 8, es sei dann, man nimmt an, Satz 6 wäre die speziellere Regel. Dann müsste die Ausgabe aber
c d ~c ~d
sein, denn der vollständige Ausdruck, in dem das temporäre Objekt Foo("c") erzeugt wird, ist das return-Statement, während die Funktion erst später verlassen wird, nämlich wenn das Ende des Blocks erreicht wird.
-
camper schrieb:
...
Getestet mit g++-Versionen 3.3.6, 3.4.6, 4.1.2, 4.2.4, 4.3.2 ergibt jeweilsa b c ~c ~b ~a d e f ~f ~e ~d
...
IBM XL C/C++ V8.0:
a b c ~c ~b ~a d e f ~f ~e [b]~f[/b] ~d
Gerade die doppelte Zerstörung vom "f"-Objekt würde mich nervös machen ...
Gruß,
Simon2.
-
Simon2 schrieb:
Gerade die doppelte Zerstörung vom "f"-Objekt würde mich nervös machen ...
Umso mehr ein Grund, niemals so etwas zu schreiben - im Gegensatz zu g++ scheint dieser Compiler es aber richtig machen zu wollen uns scheitert.
-
Bei mir ergibt obiger Code die Warnungen:
VS2005 (SP1): warning C4172: returning address of local variable or temporary
g++ V3.4.4: warning: returning reference to temporaryconst Foo& g() { { Foo d("d"); return Foo("e"), Foo("f"); // diese Zeile erzeugt die Warnung } }
Warum geben die Compiler die Warnung aus, wenn das gestattet ist?
Simon
-
theta schrieb:
Warum geben die Compiler die Warnung aus, wenn das gestattet ist?
Weil es ausnahmslos böse ist.
-
Warum geben die Compiler die Warnung aus, wenn das gestattet ist?
Es ist noch sehr viel anderes erlaubt, dass dir sehr viele Probleme bereiten kann. Sei froh drum, wenn dich dein Compiler darauf hinweist, dass es da ev. Probleme geben kann und du vlt. nochmal die Verwendung überlegen solltest.
-
Jaja, schon klar. Nur wundere ich mich, dass die Lebensdauer dieser Objekte verlängert wird, obwohl es ja eigentlich sowiso gefährlich ist (und das ist somit nicht praktikabel).
Ich verstehe nicht warum etwas eingebaut wird (die Lebenserhaltung), obwohls nicht praktikabel ist. (Die Warnung als Hinweis verstehe ich natürlich schon.)
Ist das historisch bedingt? Oder gibts Situationen wo das eine Rolle spielt (in der Praxis)?
Simon
-
camper schrieb:
Meiner Ansicht nach besteht ein direkter Widerspruch zwischen den Sätzen 6 und 8, es sei dann, man nimmt an, Satz 6 wäre die speziellere Regel. [...] denn der vollständige Ausdruck, in dem das temporäre Objekt Foo("c") erzeugt wird, ist das return-Statement, während die Funktion erst später verlassen wird, nämlich wenn das Ende des Blocks erreicht wird.
Hmm hmm hmm. Schau mal nach 6.6.3/1.
A function returns to its caller by the return statement
Und außerdem 6.6.3/3, der letzte Teilsatz gibt mir zu denken. Ich weiß nicht, ob der sich nur auf die "expression of type "cv void" bezieht, oder ob das eine generelle Aussage darstellt bzw. Implikation für eine generelle Aussage ist. Zumal dieser Teilsatz nur für die Auswertung einer "cv void"-Expression doch herzlich nutzlos wäre (?). (und, was soll cv-void überhaupt darstellen? *kopfkratz*)
A return statement with an expression of type "cv void" can be used only in functions with a return type of cv void; the expression is evaluated just before the function returns to its caller.
Das klingt für mich etwas schwammig, insbesondere in der Hinsicht, dass Objekte, die in einem return statement erzeugt wurden, erst nach Block-Ende der Funktion zerstört werden könnten. Dann gäbe es auch keinen Widerspruch in den Sätzen 6 und 8 in 12.2/5 und das Ergebnis dürfte nie c d ~c ~d sein.
Außerdem grübel ich über das hier:
reference is bound to a temporary
An anderer Stelle heißt es wieder:
temporary is bound to a reference
Macht das einen Sinn? Ist das ein semantischer Unterschied - wenn ja, welcher? Der Standard ist doch sonst so pingelig in der Wortwahl.
-
7H3 N4C3R schrieb:
camper schrieb:
Meiner Ansicht nach besteht ein direkter Widerspruch zwischen den Sätzen 6 und 8, es sei dann, man nimmt an, Satz 6 wäre die speziellere Regel. [...] denn der vollständige Ausdruck, in dem das temporäre Objekt Foo("c") erzeugt wird, ist das return-Statement, während die Funktion erst später verlassen wird, nämlich wenn das Ende des Blocks erreicht wird.
Hmm hmm hmm. Schau mal nach 6.6.3/1.
A function returns to its caller by the return statement
Das ist eher meiner ungenauen Übersetzung geschuldet.
A function returns to its caller by the return statement
Hier wir das Mittel angegeben.
A temporary bound to the returned value in a function return statement (6.6.3) persists until the function exits.
Hier geht es dagegen um einen Zeitpunkt oder einer Ort.
Und außerdem 6.6.3/3, der letzte Teilsatz gibt mir zu denken. Ich weiß nicht, ob der sich nur auf die "expression of type "cv void" bezieht, oder ob das eine generelle Aussage darstellt bzw. Implikation für eine generelle Aussage ist. Zumal dieser Teilsatz nur für die Auswertung einer "cv void"-Expression doch herzlich nutzlos wäre (?).
Damit wird zweifelsfrei festgestellt, dass dieser Ausdruck ausgewertet wird, obwohl dies höchtens wegen Seiteneffekten notwendig ist.
void evil() { return (void)*(int*)0; } // undefiniert,
(und, was soll cv-void überhaupt darstellen? *kopfkratz*)
Ein Ausdruck, der nach cv void gecastet wird, ein Aufruf einer Funktion, mit entsprechendem Rückgabetyp, ein throw-Ausdruck (der würde allerdings nicht zu einem normalen return führen) - fallen mir auf Anhieb ein.
7H3 N4C3R schrieb:
A return statement with an expression of type "cv void" can be used only in functions with a return type of cv void; the expression is evaluated just before the function returns to its caller.
Das klingt für mich etwas schwammig, insbesondere in der Hinsicht, dass Objekte, die in einem return statement erzeugt wurden, erst nach Block-Ende der Funktion zerstört werden könnten. Dann gäbe es auch keinen Widerspruch in den Sätzen 6 und 8 in 12.2/5 und das Ergebnis dürfte nie c d ~c ~d sein.
Das dürfte dort vor allem deshalb stehen, weil hier ein Unterschied zu C besteht - in C darf eine return-Statement einer void-Funktion kein Argument haben.
7H3 N4C3R schrieb:
Außerdem grübel ich über das hier:
reference is bound to a temporary
An anderer Stelle heißt es wieder:
temporary is bound to a reference
Macht das einen Sinn? Ist das ein semantischer Unterschied - wenn ja, welcher? Der Standard ist doch sonst so pingelig in der Wortwahl.
Bei einer Heirat ist es doch auch egal, ob wir sagen, dass der Mann die Frau oder die Frau den Mann heiratet.
Für Wichtiger halte ich - das hat mich ein paar Jahre beschäftigt - dass hier immer ein Verweis auf 12.2/1 mitgedacht werden muss. Es geht nicht darum, dass ein Objekt - in irgendeinem Kontext - temporär ist, sondern dass der Ausdruck, in dem das Objekt auftritt, dieses temporäre Objekt auch (im Sinne von 12.2/1) erzeugt hat.
struct Foo {}; Foo(Foo()); // Foo() erzeugt das temporäre Objekt -> RVO ist möglich Foo(Foo()=Foo()); // Der Teilausdruck Foo()=Foo() erzeugt das Objekt nicht -> RVO ist nicht möglich
Das hat allerdings nichts mit unserem Problem zu tun.
-
Danke für deine Antwort. Das klärt zumindest meine Fragen soweit, auch wenn das Problem wohl eher definitionsbedingt nicht zu klären ist.
camper schrieb:
Ein Ausdruck, der nach cv void gecastet wird, ein Aufruf einer Funktion, mit entsprechendem Rückgabetyp, ein throw-Ausdruck (der würde allerdings nicht zu einem normalen return führen) - fallen mir auf Anhieb ein.
Also irritiert hatte mich daran die cv-Qualifikation von void. War vielleicht nicht besonders gut ausgedrückt.