Funktionsweise const Parameter/Rückgabe
-
Hi,
ich habe mal eine allgemeine Frage. Was macht "const Typ&" mit den Daten genau also folgendes Beispiel z.B.:
void foobar(const KlasseA& data); const KlasseA& getFoobar(); ... int main() { KlasseA test; foobar(test); test = getFoobar(); }Werden die Daten beim Aufruf von foobar intern kopiert? Oder ist das nur eine Referenz auf test? Wie perfomant ist das? Ist das guter oder schlechter Stil?
Ist das Equivalent zu foobar(const KlasseA* data)?Gruss
niv
-
Es wird eine Referenz zurückgegeben. Da diese const ist, kann das referenzierte Objekt nicht geändert werden. Wenn es nötig ist, das Objekt herauszugeben, ist das die beste Möglichkeit. Noch besser ist nur, wenn die Rückgabe überhaupt nicht nötig ist, um die Kapselung zu verbessern.
Viele hier ziehen Referenzen den Zeigern vor, da man nicht unbedingt die Zeigerfunktionalitäten braucht. Referenzen haben nur den Nachteil nicht mehr verändert werden zu können und somit nicht von Anfang an nullptr sein zu können (was aber auch der Vorteil ist). Für call by reference-Übergaben sind sie daher womöglich zu bevorzugen. Es gibt aber auch ein paar Zeiger-Verfechter, ich kenne aber deren Argumente gerade nicht.
Äquivalenz zu const-Zeiger-Übergabe passt.
-
niv schrieb:
Was macht "const Typ&"
Es deklariert eine "Referenz-auf-const-Typ".
niv schrieb:
folgendes Beispiel z.B.:
void foobar(const KlasseA& data); const KlasseA& getFoobar(); ... int main() { KlasseA test; foobar(test); test = getFoobar(); }Werden die Daten beim Aufruf von foobar intern kopiert?
Nein. 'data' wird nur ein neuer, zusätzlicher Name für das Objekt, was hinter 'test' steckt.
niv schrieb:
Oder ist das nur eine Referenz auf test? Wie perfomant ist das? Ist das guter oder schlechter Stil?
1. Ja, 2. Da passiert nix dolles, ist also relativ billig. 3. Es kommt drauf an, was Du machen willst und ist eigentlich keine Stilfrage.
niv schrieb:
Ist das Equivalent zu foobar(const KlasseA* data)?
äquivalent nicht. Aber sehr ähnlich. Das, was der Compiler auf Maschinenebene draus macht, sollte äquivalent sein. Weil data hier aber ein Zeiger ist, musst Du ihn innerhalb der Funktion ständig dereferenzieren, um auf das KlasseA-Objekt zugreifen zu können. Du kannst Dir Referenzen als automatisch dereferenzierende Zeiger vorstellen, die man nachträglich nicht mehr ändern kann. Also, folgendes ist auf Maschinenebene äquivalent:
void test1() { int i = 23; int &r = i; r += 19; } void test2() { int i = 23; int *const p = &i; *p += 19; }Wenn Du die Wahl zwischen "Typ", "const Typ&" bzw "Typ const&" als Funktionsparameter hast, dann solltest Du eine Referenz nur nehmen, falls entweder sizeof(Typ) "recht groß" ist oder falls Typ nicht mehr "trivial-kopierbar" ist. Beispielsweise ist std::string unter G++ ein recht kleines Objekt (4 Bytes auf einer 32-Bit PC-System). Es ist aber nicht trivial-kopierbar, da die Klasse einen benutzerdefinierten Kopierkonstruktor und Destruktor hat.
In generischem Code kann man teilweise boost::call_traits<T>::param_type als Funckions-Parameter-Typ verwenden. Das ist je nach Situation entweder "const T" oder "const T&". Ich glaube, diese Entscheidung hängt nur davon ab, ob T ein Klassentyp ist oder nicht.
Pass-by-value ist aber auch nicht sooooo schlimm und manchmal sehr nützlich auch wenn der Typ einen nicht-trivialen Kopierkonstruktor besitzt. Siehe
http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/
Da gibt es so einige Optimierungstricks, die explizit vom C++ Standard erlaubt werden. Beispielsweise ist folgende Funktion:string flip(string const& x) { string result = x; reverse(result.begin(),result.end()); return result; }nicht ganz so effizient in allen Fällen wie die hier:
string flip(string x) { string result; result.swap(x); reverse(result.begin(),result.end()); return result; }(einen modernen Compiler und ein schnelles swap vorausgesetzt)
Wenn Du also intern eine Kopie erzeugen musst, könnte sich die Übergabe per value lohnen. Wenn man das mit dem Kopieren dem Compiler überlässt, kann der es ggf wegoptimieren.
Im Zuge vom kommenden C++ Standard (C++11), sollte pass-by-value auch wieder etwas populärer werden. Für viele Klassen bietet sich ein schneller no-throw Move-Konstruktor an, mit dem unnötiges Kopieren reduziert werden kann.