Rückgabe einer Membervariablen als Referenz
-
Hallo Leute,
mich als Anfänger würde es mal interessieren, wie sicher Rückgabewerte als Referenzen sind.
Beispiel:#include <iostream> class test { protected: std::string name; public: test() : name("Irgendwas...") {} const std::string& toString() const; }; const std::string& test::toString() const{ return name; } int main(int argc, char **argv) { std::string str; if (true){ test temp; // lokales Objekt temp str = temp.toString(); } // lokales Objekt temp ist nicht mehr gültig std::cout << str << std::endl; return 0; }
Der Compiler gibt keine Warnung aus. Aber str ist am Ende des Programms eine Referenz auf die Membervariable test::name des lokalen Objekts temp, welche nur innerhalb des if-Blocks gültig war. Zeigt dann nicht str auf einen ungültigen Speicherbereich?
-
Das Objekt wird zwar gelöscht, aber vorher wird eine Kopie der Membervariable in
std::string str
gespeichert. In dem Fall zeigt der interne Zeiger vonstr
nicht auf die Membervariable, sondern auf ihre Kopie. Ich lasse mich aber gerne eines besseren belehren.
-
Nö, das ist so einwandfrei.
test::toString()
gibt zwar eine Referenz zurück, aber diese Referenz wird als Argument des Zuweisungsoperators vonstd::string
benutzt, um den Inhalt zu kopieren. Auf was der interne Zeiger vonstr
zeigt lässt sich nicht sagen, da das Implementationsdetails derstd::string
Klasse sind.
-
OT:
In C++ ist es möglich und auch üblich einen neuen Scope einfach mit {} zu definieren.
Ein unsinniges Dummy-Konstrukt wie if(true) { ist daher nicht nötig.
-
Vielen Dank! Es liegt also, wenn ich es richtig verstanden habe, an der Definition des Zuweisungsoperators. Da der Zuweisungsoperator der Klasse
std::strg
so implementiert wurde, dass er nicht einfach nur Zeiger kopiert sondern gleich das gesamte string-"Feld".
Aber wäre es auch denkbar, dass irgendeine fiktive Klasse den Zuweisungsoperator anders implementiert, so dass die Rückgabe als Referenz risikoreich ist? Oder ist es generell gefahrlos eine Referenz zurückzugeben, wenn es sich bei der referenzierten Variablen um eine Membervariable handelt?
-
Wenn du das hier machst:
test temp; const std::string &str = temp.toString();
oder das:
test temp; const std::string *str = &temp.toString();
dann hast du genau die Situation, die du wolltest, dass nämlich deine lokale Variable auf die Membervariable zeigt und keine Kopie ist.
Ob man das benutzen sollte? Ich würd's nicht tun. Wenn du eine Referenz einer Membervariable zurückgibst, kannst du nicht ausschließen, dass der Programmierer, der deine Klasse benutzt, einen Pointer erstellt, dessen Lebensdauer länger ist als die Lebensdauer des Objekts:
const std::string *str; { test temp; str = &temp.toString(); } // <-- Hier wird temp zerstört. std::cout << *str; // BÄMM!
-
Ich würd´s schon tun, aber es hängt halt immer vom Kontext ab. Angenommen, du gibst nicht
std::string
, sondernSuperDuperIrreGrossesRiesigesDing
zurück, das teuer zu kopieren ist, dann macht es Sinn, das Objekt per Referenz zurückzugeben. Damit erkaufst du dir Geschwindigkeit zum Preis von Benutzbarkeit, da muss man dann entscheiden, was wichtiger ist.
-
Dann werde ich mal die Rückgabe von Referenzen vermeiden. Aber wie sieht es mit dem Laufzeitnachteil aus? Schließlich könnte doch die Membervariable, die zurückgegeben werden soll, viel Speicher belegen, so dass bei jeder Rückgabe zeitaufwändig kopiert würde.
-
sorry... ich war zu langsam... da ist ja schon meine Antwort...
-
Gib ruhig eine Referenz zurück, das ist sicher. Wer solche Pointerzaubereien wie oben macht ist selbst Schuld. Nur mal ein Beispiel:
class Blubb { std::string s; public: std::string blobb() const { return s; } }; int main() { std::string *s; Blubb blibb; s = &blibb.blobb(); // Oups }
-
Ich find's ja sauberer den Member direkt public zu machen, statt da ne Referenz zurückzugeben aus ner Funktion, ist im Prinzip das gleiche.
-
Decimad schrieb:
Ich find's ja sauberer den Member direkt public zu machen, statt da ne Referenz zurückzugeben aus ner Funktion, ist im Prinzip das gleiche.
Nein, ist es nicht.
class Blubb { std::string s; public: std::string const& blobb() const { return s; } };
-
Decimad schrieb:
[...] ist im Prinzip das gleiche.
-
cooky451: OK. Wenn ich es richtig verstehe:
Es wird zwar keine Referenz zurückgegeben. Trotzdem höchst fragwürdige Übergabe einer temporären Kopie an eine Referenzvariable möglich...
Eines würde mich aber noch interessieren: Wenn ich den Rückgabewert nicht als Referenz deklariere, heißt das dann automatisch, dass auch tatsächlich kopiert wird? Oder anders gefragt: Können Compiler erkennen, wann eine Kopie eigentlich überflüssig ist? Können Compiler überflüssige Kopiervorgänge "wegoptimieren"?
-
Jau, können sie und machen die meisten auch. Das nennt sich dann "return value optimization" (RVO) und "named return value optimization" (NRVO). Du musst ggf. die Optimierungseinstellungen deines Compilers anpassen, aber im Release Modus mit höchstmöglichen Optimierungseinstellungen sollte jeder halbwegs gute Compiler das wegoptimieren.
Ob optimiert werden kann weiß der Compiler am besten, das bekommst du als Anwender oft nicht mit. Um sicherzugehen könnte man den erzeugten Assembler Code angucken und den überprüfen. Manchestd::string
Implementationen benutzen Copy-On-Write Techniken, bei denen tatsächlich nur Pointer kopiert werden und erst beim ersten schreibenden Zugriff eine Kopie des Originals erstellt wird. Das ist allerdings implementationsabhängig und vom Standard nicht vorgeschrieben.
-
keine_ahnung98258495 schrieb:
Können Compiler überflüssige Kopiervorgänge "wegoptimieren"?
http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/
http://en.wikipedia.org/wiki/C%2B%2B11#Rvalue_references_and_move_constructors <- C++11Edit:
keine_ahnung98258495 schrieb:
Übergabe einer temporären Kopie an eine Referenzvariable
Referenzen != Pointer, zumindest was die Sprachsemantik angeht. Denn mit Referenzen geht das AFAIK nicht, da diese ja sofort initialisiert werden müssen und somit auch nicht außerhalb des Scopes definiert werden können. Solche Konstrukte funktionieren also nur über Pointer, nicht aber über Referenzen.
(Mit Membervariablen funktioniert das trotzdem, aber das ist noch mal eine andere Geschichte.)
class A { std::string& s_; public: A(std::string& s) : s_(s) {} void print() { std::cout << s; } }; int main() { A a(std::string("Hm..")); a.print(); // Autsch }
-
Swordfish:
Was hat denn bitte die Rückgabe einer Referenz auf einen Datenmember mit Datenkapselung zu tun? Die Referenz kann nur auf einen Datenmember der Klasse zeigen (wäre es eine lokale Variable innerhalb der Methode -> Bam). Jetzt erzähl mir hier bitte mal nix von wegen ich hätte Datenkapselung nicht verstanden. Bevor ich da eine Referenz zurückgebe um ach so viel Performance rauszukitzeln, mach ich den Member public, punkt aus.
Natürlich wäre meine erste Wahl, die Implementierung hinter der Schnittstelle verbergen (fällt aber gerade aus, weil das nicht das Thema ist offenbar). Mit dieser Referenzsache legt ihr ja praktisch die Implementierung offen (und man sorgt für Lebenszeit-Probleme).
-
cooky451: Vielen Dank! Ich habe aber noch ein Problem mit deinem letzten Beispiel: Dem Konstruktor von A soll doch eine Referenz auf ein
std::string
übergeben werden. Muss da nicht ein left-value übergeben werden? Irgendwie kriege ich das nicht kompiliert...no matching function for call to ‘A::A(std::string)’
Wenn ich mir zuvor ein left-value in Form einer string-Variablen anlege, und die dann übergebe, läuft es. Aber dann sieht man irgendwie nicht die Problematik...
-
Decimad schrieb:
Swordfish:
Was hat denn bitte die Rückgabe einer Referenz auf einen Datenmember mit Datenkapselung zu tun? Die Referenz kann nur auf einen Datenmember der Klasse zeigen (wäre es eine lokale Variable innerhalb der Methode -> Bam). Jetzt erzähl mir hier bitte mal nix von wegen ich hätte Datenkapselung nicht verstanden. Bevor ich da eine Referenz zurückgebe um ach so viel Performance rauszukitzeln, mach ich den Member public, punkt aus.Die Übergabe als Referenz (besonders als Referenz auf const) hat nichts mit Datenkapselng zu tun - die Definition als public dagegen schon. (die Übergabe als "einfache" Referenz ist da grenzwertig)
-
keine_ahnung98258495 schrieb:
Aber dann sieht man irgendwie nicht die Problematik...
Ja, da fehlt ein const. http://ideone.com/PyUN4
Lustigerweise kompiliert es mit MSVC auch ohne.
-
cooky451: Ah, jetzt hab ich's. Finde ich interessant...