Kritik eines FAQ-Beitrages
-
ich hab mal ein bißchen in alten faqs gestöbert. dabei fällt auf, dass insbesondere einige der älteren beiträge durchaus noch den einen oder anderen kommentar vertragen könnten. zudem erscheinen mir einige der ältesten treads kaum relevant genug, um in der faq zu erscheinen. jedenfalls hab ich mir mal den hier rausgepickt:
Referenz auf ein temporäres Objekt?
ich zitier mal den wesentlichsten beitrag:HumeSikkins schrieb:
Hallo,
also manchmal sehe ich den Wald vor lauter Bäumen nicht. Und dann beim Zähneputzen wird alles glasklarAlso:
class X {...}; X& Func(X& x) { return x; } int main() { X& x = Func( X() ); // ... x.IrgendEineMethode(); }Der Code ist illegal. Nicht auf Grund von Lebenszeiten, sondern schlicht und einfach weil eine nicht-konstante Referenz nicht an das unbenannte Objekt gebunden werden darf.
Code wie dieser:
const string& Func(const string& str) { cout << "in Func" << endl; return str; } int main() { const string& x = Func ( string("test") ); cout << x << endl; }ist illegal, da die Lebenszeit des unbenannten Objekts nur von der Referenz abhängt, an die es gebunden wird. Eine Kopie der Referenz hat auf das Objekt keinen Einfluß. Zum Zeitpunkt des couts existiert also schon lange kein Objekt mehr.
Fazit:
1. Die Rückgabe von Referenzparametern als (konstante) Referenz ist sicher.2. Die Rückgabe von konstanten Referenzparametern als konstante Referenz ist nicht sicher, da ungültige Referenzen entstehen können.
Gleich als Regel:
Gebe niemals einen konstanten Referenzparameter als konstante Referenz zurück.am code und der erklärung gibt es nichts auszusetzen, wohl aber an den schlussfolgerungen.
1. Die Rückgabe von Referenzparametern als (konstante) Referenz ist sicher.
std::string& foo(std::string& s) { return s; } int main() { std::string& s = foo( std::string() = "test" ); std::cout << s; }natürlich kann man argumentieren, dass man so etwas nicht schreiben sollte. und da stimme ich zu. trotzdem bleibt festzuhalten, dass foo keinerlei kontrolle über die lebenszeit der objekte hat, die an ihren parameter gebunden werden. das ganze erinnert ein bisschen an das beispiel, mit dem Stroustrup in TC++PL temporäre objekte einführt:
void f(string& s1, string& s2, string& s3) { const char* cs = (s1+s2).c_str(); cout << cs; if (strlen(cs=(s2+s3).c_str())<8 && cs[0]=='a') { // cs used here } }hier geht es zwar nicht um die rückgabe von referenzen. der punkt ist aber der, dass die eigenschaft, temporär zu sein, einem objekt nicht an sich zuteil wird, sondern nur in einem kontext ensteht (der standard definiert auch nicht, was ein temporäres objekt ist, er sagt nur, wann sie entstehen). innerhalb von foo ist das objekt, an das s gebunden wird, nicht temporär. was foo garantieren kann (was irgendeine sauber geschriebene funktion garantieren kann) ist, dass ihr rückgabewert gültig bleibt bis zum ende des ausdrucks, in dem der funktionsaufruf stattfand. nicht mehr und nicht weniger. der typ des rückgabewertes ist dafür völlig unerheblich.
2. Die Rückgabe von konstanten Referenzparametern als konstante Referenz ist nicht sicher, da ungültige Referenzen entstehen können.
der rat ist richtig, aber die begründung nicht ausreichend. das mögliche entstehen einer ungültigen referenz ist direkte folge des aufrufenden code und dort erkennbar. es hat also an sich nichts mit der funktion selbst zu tun. wäre das problem der funktion anzulasten, so dürften z.b. memberfunktionen niemals referenzen auf *this zurückgeben.
den fall gegen die rückgabe eines konstanten referenzparameters würde ich aus einem anderen grund machen. wann reichen wir denn üblicherweise parameter weiter? immer dann, wenn funktionsaufrufe (potentiell) verkettet werden sollen. z.b.
std::cout >> a >> b;der operator >> reicht jeweils seinen linken parameter weiter. die wesentliche aufgabe des operators ist es, etwas aus den stream zu lesen, diesen also zu verändern. eine referenz auf const kommt somit gar nicht in betracht. ähnlich ist es bei memberfunktionen wie z.b. copy-assignment. diese funktion kann sinnvoll nur nicht-const sein. es stellt sich also die frage, welche sinnvolle aufgabe eine funktion, die einen konstanten referenzparameter bekommt und diesen zurückgibt haben kann. die funktion kann ja ihr ergebnis nicht direkt kommunizieren. damit bliebe noch die veränderung eines globalen zustands. der sinn eines funktionswertes ist ja eigentlich, ein ergebnis oder eine veränderung zu kommunizieren. gebe ich einen konstanten parameter direkt weiter, passiert genau das nicht.
ist wahrscheinblich alles ein bisschen umständlich dargestellt. meinungen?
-
Zu 1: Deine Funktion ist genauso illegal wie das Beispiel von HumeSikkins - du übergibst eine temporäre Variable an einen Referenzparameter.
Zu 2: Nicht-Konstante Referenzparameter kannst du auch problemlos weitergeben - der Standard stellt afaik sicher, daß du an einen "T&" kein temporäres Objekt übergeben darfst, also bleibt die dahinterliegende Variable auch im Kontext der umgebenden Funktion gültig (und deine Beispiele iostreams und op= verwenden üblicherweise nichtkonstante Referenzen).
Konsante Referenzparamter sind von der Funktion tatsächlich nicht zu beeinflussen - und von daher macht es (abgesehen von den Temporary-Problemen) tatsächlich wenig Sinn, diese wieder zurückzugeben.
-
CStoll schrieb:
du übergibst eine temporäre Variable an einen Referenzparameter.
und das ist durchaus NICHT illegal. was der standard verbietet, ist, dass ein rvalue an eine referenz auf non-const gebunden wird. das ist etwas (fast) ganz anderes. jedenfalls wird in diesem falle das ergebnis des =operators (das ein lvalue ist) an den parameter der funktion gebunden. wenn das tatsächlich illegal wäre, solltest du das durch ein entsprechendes zitat des standards untermauern können. die behauptung, man könnte temporaries nicht an referenzen auf nicht-const binden, wird schon durch das gewöhnliche catch mit einer referenz auf nicht-const widerlegt.
-
@camper
Das der Beitrag keine FAQ ist sehe ich ein. Der stammt ja auch noch aus einer Zeit, in der FAQs häufig auch einfach lebendige/interessante/relevante Diskussionen zwischen mehreren Leuten waren, wo am Ende irgendwelche Groschen gefallen sind. Mittlerweile sind wir mehr zum "ich-erklär-euch-mal-was"-Stil übergegangen (wobei mit "ich" nicht ich gemeint bin sondern der jeweeils erklärende) und unter diesem Gesichtspunkt würde ich den genannten Beitrag schlicht löschen.Deine Kritikpunkte kann ich ehrlich gesagt nicht nachvollziehen. Beide Aussagen finde ich als Daumenregeln (und als solche sind sie gedacht) nach wie vor sinnvoll. Das man Gegenbeispiele konstruieren kann, hat imo keinen Einfluss auf die Bedeutung der Daumenregeln. Wichtig ist, dass die Mehrzahl der Fälle gut behandelt wird: und einen Parameter den man als const-Ref erhalten hat als const-Ref zurückzugeben ist im Allgemeinen einfach nicht sinnvoll und gefährlich.
Btw: Marshal Cline et al. haben in ihrem Buch "C++ FAQs" folgenden Eintrag:
FAQ 32.08
Q:Should a parameter passed by const reference be returned by const reference?
A:
No; it might create a dangling reference, which could destroy the world.
[...]
Note that if a function accepts a parameter by non-const reference[...], returning a copy of this reference paramater is safe because a temporary cannot be passed by non-const reference.Merkregeln, die für die wahrscheinlich bekannteste C++ FAQ der Welt ok sind, sollten imo auch für dieses Forum ok sein.
@CStoll
campers Beispiel 1) ist durchaus gültiges C++. Das ist der Punkt mit dem "blows your whole leg off." Wenn man sich in C++ etwas anstrengt, fliegen die Beine nur so durch die Gegend
-
ich habe ja gar nichts gegen die regeln. nur gegen die argumentation, die zu diesen regeln führt, halte ich für nicht ganz stichhaltig und wollte das mal erwähnt haben. und auch auch renomierte authoren können sich mal irren. der standard selbst hat ja ein paar 100 defekte - und darüber haben sich viele kluge leute lange den kopf zerbrochen. das beispiel mit der zuweisung war noch sehr deutlich. ein weitaus weniger offensichtlich esoterisches beispiel entstünde, falls wir das "name-constructor" idiom einsetzen (wenn ich mich nicht irre, wird das in dieser faq auch mal erwähnt). a la
struct X { X() {} int a_, b_, c_; X& set_a(int a) { a_ = a; return *this; } X& set_b(int b) { b_ = b; return *this; } X& set_c(int c) { c_ = c; return *this; } }; X& foo(X&); int main() { X& x = foo( X().set_a( 0 ).set_b( 1 ).set_c( 2 ) ); // benutze x - ups ]hier bauen wir unser X ganz so wie es gedacht ist. und trotzdem ist das ergebnis eine katastrophe

-
Hi camper,
was schlägst du also konkret vor? Möchtest du den FAQ-Eintrag um einen Beitrag ergänzen? Willst du den vorhandenen Eintrag editieren? Soll ich den gesamten Eintrag löschen?
-
das überlass ich dir. eine entsprechende ergänzung richtet sicher keinen schaden an.
-
Hallo,
mir scheinen zwei Wege sinnvoll:
a) du schreibst eine Ergänzung zu dem vorhandenen Beitrag.
b) ich lösche den alten Thread und du schreibst einen neuen Beitrag, der das Thema erklärt.a) kann man wohl auf zwei Arten realisieren. Entweder lässt du mir deine Ergänzung zukommen und ich füge sie dann an (Nachteil: Das Posting läuft dann unter meinem Namen) oder ich verschiebe den Beitrag hier her, du ergänzt und ich verschiebe zurück.
-
oder du ergänzt den beitrag schlicht mit einem zitat
-
camper schrieb:
oder du ergänzt den beitrag schlicht mit einem zitat
Ok, das übersteigt so erstmal mein Verständnis (setzt wohl einen IQ > 20 voraus). Kannst du das nochmal etwas genauer erklären?
Wo muss ich was anklicken? Was soll ich wie zitieren?
-
du kannst doch in diesem thread zitieren. anstatt das dann aber hier zu posten, übeträgst du das zitat dann per c&p schlicht in den anderen thread.
-
@camper: Nachdem ich seit gestern versuche, dahinterzukommen, worum es geht wäre es mir sehr lieb wenn du etwas deiner Zeit opfern würdest, und dieses Thema in einem neuen Thread für die FAQ nochmals aufrollen könntest.
HumeSikkins schrieb:
Ok, das übersteigt so erstmal mein Verständnis (setzt wohl einen IQ > 20 voraus). Kannst du das nochmal etwas genauer erklären?
Wo muss ich was anklicken? Was soll ich wie zitieren?*lol*
Greetz, Swordfish
-
std::string& s = foo( std::string() = "test" );das können wir etwas anders schreiben:
std::string& s = foo( std::string().operator=("test") );operator= hat diese signatur:
std::string& std::string::operator=(const char*)der wesentlich grund, warum das alles so geht ist der, dass der objektparameter einer memberfunktion auch ein rvalue sein darf. innerhalb derfunktion ist *this aber ein lvalue (und was dann anschließend damit passiert unterliegt keinen besonderen beschränkungen, insebondere kann es für den funktionswert benutzt werden). dabei wird aber kein (weiteres) temporary erzeugt. in gewisser weise erkennt man hier, dass das klassenkonzept erst nachträglich in die sprache aufgenommen wurde, es kollidiert in einigen punkten mit dem relativ simplen konzept von l- und rvalues. rvalues von klassenobjekten sind ja auch die einzigen rvalues, die cv-qualifiziert sein können - eben genau deshalb, weil man auf umwegen daraus wieder lvalues erhalten kann. denn const und volatile sind ja letztlich eigenschaften, die nur lvalues sinnvoll zukommen können.
-
Hallo camper,
schreibst du hier noch was, oder soll ich deine vorigen Beiträge so wie sie sind in den FAQ-Thread reinzitieren?
-
HumeSikkins schrieb:
Hallo camper,
schreibst du hier noch was, oder soll ich deine vorigen Beiträge so wie sie sind in den FAQ-Thread reinzitieren?
ich hab nichts mehr hinzuzufügen. zitier mal los

-
Done. Falls noch was wichtiges fehlt, einfach bescheid sagen.