PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : [C++] Unterschied zwischen reference und const reference?


Nasenbaer
2008-10-05, 15:54:05
Was ist bei Funktionen eigentlich der Unterschied zwischen Reference und Const Reference. Was ne Referenz selbst ist ist mir klar.
Bedeutet das hier z.B.

void test(const Class &object) { /* .. */ }

,dass man einfach object nicht auf ein anderen Objekt "umhängen" darf?

Und bei Rückgabewerten von Funktionen hab ich auch schon gesehen, dass ne const reference zurückgegeben wurde? Welchen Sinn hat das?

ManuelCalavera
2008-10-05, 16:12:20
const bedeutet das die funktion das objekt nicht ändern kann bzw. das ein konstantes objekt übergeben werden kann.


Beispiel (ist mit pointern, aber erfüllt den gleichen zweck):

void f (char* c) { ... }
void g (const char* c) { ... }

Aufruf von:

f("test"); <- Fehler da der string "test" const ist f aber kein const akzeptiert
g("test"); <- kein problem, da g const strings akzeptiert




Referenzen können (imo) nicht umgehängt werden.

Trap
2008-10-05, 17:18:53
Ein const x& verhält sich wie ein const x (bis aufs Kopieren).

Nasenbaer
2008-10-05, 17:29:47
Ich hab mir den Artikel C++ Const Correctness (http://www.linuxjournal.com/article/7629) gerade durchgelesen und es jetzt verstanden - denke ich jedenfalls.

1. Man nutzt f(const Type& obj) um zu signalisieren, dass f() auf obj nur lesend zugreifen wird.
2. f() kann dann nur noch Methoden von Type aufrufen, die const sind.
3. Ich kann z.B. f() eine const reference zurückgeben lassen um dem Rufer Lese-Zugriff auf die Rückgabe zu ermöglichen und erspare mir eine Kopie bzw. die Möglichkeit (bei Rückgabe eines Pointers z.B.), dass der Rufer, die Werte verändert.

an.zgep
2008-10-14, 19:19:42
3. ist nicht ganz korrekt, denn mit "Class const * const object" hat man das selbe wie bei "Class const & object" mit einem Pointer.

ich würde übrigens generell empfehlen, das const immer nachzustellen. Sonst verwechselt man so Zeug wie "Class const * object" mit "Class * const object". Das erste ist ein konstanter Zeiger auf ein Objekt der Klasse Class (also eigentlich das selbe wie eine Referenz auf ein Objekt der Klasse Class), das zweite ein Zeiger auf ein konstantes Objekt der Klasse Class.

Als hässliche Alternative bietet sich sonst noch "const Class * const object" an.

Nasenbaer
2008-10-14, 19:37:28
Ja da hast du natürlich Recht - ist auch so in dem verlinkten Artikel beschrieben.

an.zgep
2008-10-14, 19:42:09
sry, nach 7h vorlesungen war ich zu faul links zu öffnen ^^

Gast
2013-01-24, 17:38:48
3. ist nicht ganz korrekt, denn mit "Class const * const object" hat man das selbe wie bei "Class const & object" mit einem Pointer.

ich würde übrigens generell empfehlen, das const immer nachzustellen. Sonst verwechselt man so Zeug wie "Class const * object" mit "Class * const object". Das erste ist ein konstanter Zeiger auf ein Objekt der Klasse Class (also eigentlich das selbe wie eine Referenz auf ein Objekt der Klasse Class), das zweite ein Zeiger auf ein konstantes Objekt der Klasse Class.

Als hässliche Alternative bietet sich sonst noch "const Class * const object" an.

Leichenfledderer hin oder her, ich sehe, dass der Thread bereits vier Jahre alt ist. Jedoch erscheint er als erstes Suchergebnis, wenn man bei Google "c++ const reference" eintippt und muss daher korrigiert werden.

Die Beispiele sind genau vertauscht, 'Class * const object' ist ein konstanter Zeiger, auf ein veränderliches 'Class'. Im Prinzip das gleiche wie 'Class & object'.

Das Schlüsselwort 'const' bezieht sich immer auf das, was links von ihm steht, es sei denn, es steht bereits ganz links, dann bezieht es sich auf das, was rechts davon steht.

Viele Grüße

Gnafoo
2013-01-28, 01:41:27
Außerdem sollte man an der Stelle noch einen wichtigen Punkt erwähnen: temporäre Objekte darf man nur als const-Referenz übergeben, nicht jedoch als „gewöhnliche“ Referenz.

Beispiel:

struct triple
{
triple(int x, int y, int z): a(x), b(y), c(z) {}
int a, b, c;
};

int sum(const triple &trip)
{
return trip.a + trip.b + trip.c;
}

int main(void)
{
std::cout << sum(triple(1, 2, 3)) << std::endl;
return 0;
}


So etwas ist nur erlaubt, wenn die Referenz beim „trip“-Parameter const ist.

Grundkurs
2013-01-28, 11:10:00
const Referenzen als Parameter dienen auch folgendem Umstand:
Man kann Daten an eine Funktion im Prinzip auf drei Arten übergeben:

1. function(&Objekt); << per Pointer, also übergibt man die Adresse.
2. function(Objekt); << per Referenz.
3. function(Objekt); << per Kopie.

"Problematisch" ist dabei, dass bei Nr. 2. und 3. vom Funktionsaufruf her nicht klar ist, ob das Objekt per Referenz übergeben wird oder ob die Daten direkt kopiert werden. Bei Nr.1 wird durch den &-Operator angedeutet, dass nicht kopiert wird sondern die Adresse des Objekts übermittelt wird. Um beim Aufruf nach Nr. 2 oder Nr.3 zu wissen, wie genau das Objekt übergeben wird (also per Referenz oder Kopie) muss man erst in den Parameter der Funktion schauen.
Indem man nun in der Parameterliste das Objekt auf const setzt, sprich "void function(const Objekt& objekt){}", kann es nicht verändert werden, was den gleichen Effekt hätte wie wenn man eine Kopie übergibt (da wird das ursprüngliche Objekt auch nicht verändert da nur eine Kopie erstellt und übergeben wird). Somit ergibt sich:
Will man das ursprüngliche Objekt in der Funktion verändern, sollte man es möglichst per Pointer übergeben (was durch das &Objekt) im Funktionsaufruf klar sichtbar ist. Soll es nicht verändert werden, übergibt man es ohne &-Operator. Größere Objekte dann per const reference damit das flott passiert und kleine können auch einfach kopiert werden. Beim Funktionsaufruf sieht man dadurch sofort: Ok bei "void function(&Objekt);" wird das Objekt übergeben und verändert, bei "void function(Objekt);" passiert nichts mit dem Objekt und ob es nun per Referenz übergeben oder kopiert wird, ist eine Detailfrage, der man sich halt widmet wenn man die Funktion implementiert ("wie will ich das Objekt am besten übergeben, per Kopie oder Referenz?"). Es dient letztlich auch der besseren Lesbarkeit.

Nasenbaer
2013-01-28, 13:48:23
@Grundkurs

Du hast hier Signatur und Aufruf von Funktionen bunt gemischt und da sieht dann kein Anfänger mehr durch.
Deine Auflistung von 1-3 sind Signaturen, weil das void vorne mit angegeben wurde. Du meintest aber wohl, dass man bei einem Aufruf einer Funktion nicht sehen kann man ein übergebenes Objekt als Kopie oder Referenz übergibt. Aber das ist heutzutage auch nicht mehr das Problem. Dank moderner IDE bekommt man sowas aber schnell raus. Und für größere Projekte wird man sicher ohnehin pure Pointer vermeiden wollen und lieber irgendeine Smart-Pointer-Variante nutzen, wenn es nicht performancekritisch ist.

Grundkurs
2013-01-28, 14:51:22
@Grundkurs

Du hast hier Signatur und Aufruf von Funktionen bunt gemischt und da sieht dann kein Anfänger mehr durch.

Ja da hast du natürlich recht, ich sollte das nächste Mal wohl besser den Autopiloten ausschalten wenn ich was poste ;-) Hab das geändert um niemanden zu verwirren.

Dank moderner IDE bekommt man sowas aber schnell raus. Und für größere Projekte wird man sicher ohnehin pure Pointer vermeiden wollen und lieber irgendeine Smart-Pointer-Variante nutzen, wenn es nicht performancekritisch ist.
Klar kriegt man das durch die IDE angezeigt, aber dafür muss man halt auch erst mit dem Cursor hin etc. Wenn man es so nutzt wie ich es meinte sieht man es halt schon direkt beim ersten Blick auf den Code was mit den übergebenen Objekten "passieren" wird. Mit Smart-Pointern habe ich übrigens noch keine Erfahrung.

Nasenbaer
2013-01-28, 19:20:12
Klar kriegt man das durch die IDE angezeigt, aber dafür muss man halt auch erst mit dem Cursor hin etc. Wenn man es so nutzt wie ich es meinte sieht man es halt schon direkt beim ersten Blick auf den Code was mit den übergebenen Objekten "passieren" wird. Mit Smart-Pointern habe ich übrigens noch keine Erfahrung.
Ja man muss ggf. schauen, wie das Objekt übernommen wird aber Referenzen sind halt sicherer als Pointer: Man muss in der Funktion nicht auf nullptr/NULL testen und kann auch nicht versehentlich im Funktionsrumpf die Adresse überschreiben, statt auf den Inhalt zuzugreifen und ähnliche Flüchtigkeitsfehler. Die sind zwar sicher selten aber können auch passieren.

Beide Variante haben durchaus ihre Vor- und Nachteile. Aber warum unterscheidest du da eigentlich zwischen großen und kleinen Objekten für die Wahl der Übergabeart Pointer vs. Reference?

Grundkurs
2013-01-29, 01:06:13
Aber warum unterscheidest du da eigentlich zwischen großen und kleinen Objekten für die Wahl der Übergabeart Pointer vs. Reference?
Da hast du mich missverstanden. Ich unterscheide bezüglich der Größe nicht zwischen Pointer vs. Reference sondern zwischen: Pointer/Reference vs. Kopie. Angenommen du hast ein sehr großes Objekt. Du willst damit etwas in einer Funktion anstellen. Ob du es per Reference übergibst oder nur einen Pointer der auf das Objekt zeigt der Funktion übergibst ist geschwindigkeitstechnisch wurscht. Eine Kopie des (großen Objekts) dauert hingegen natürlich länger. Und wenn man das Objekt in der Funktion eh nicht ändern möchte (man würde ja nur die Kopie ändern), dann kann man es auch gleich als const reference übergeben. Ob Kopie oder Reference macht in der Funktion selbst optisch (bis auf die Parameterliste) keinen großen Unterschied, da man die Member über den member-operator "." anspricht (z.b: "objekt._speed = 15;") Und hier können sich dann bei größeren Funktionen schnell Fehler einschleichen. Auf der Aufrufseite sieht es so aus:
SetColor(Objekt);
PrintProperties(Objekt);
DoThis(Objekt);
DoThat(Objekt);
Schubidubi(Objekt);
Nun ist irgendwo was schiefgelaufen und das Objekt wurde manipuliert...welche Funktion hat nun ausversehen Daten verändert? Wenn du alle per const reference übergeben hast, kann dies gar nicht erst passieren. Wenn man das Objekt konsequent zu den Funktionen, die es ändern sollen, per Pointer übergibt sieht es dann so aus:

SetColor(&Objekt);
PrintProperties(Objekt);
DoThis(Objekt);
DoThat(Objekt);
Schubidubi(Objekt);

Hier ist auf den ersten Blick klar, es kann nur die SetColor-Funktion sein, die etwas am Objekt verändert hat und der Fehler wird sicher genau dort liegen. Und in der Funktion selbst hat man nochmal die zusätzliche Hürde, dass man nicht ausversehen über den member-zugriffsoperator "." einen Wert verändern kann wie man es von Variablen gewöhnt ist, sondern man muss mit dem "->" ranklotzen. Es macht insgesamt den Code sicherer und lesbarer. So war das gemeint, hoffe es ist jetzt klar. Frohes Coden!

Nasenbaer
2013-01-29, 10:59:43
Ok, dann hab ich dich falsch verstanden. Ich dachte wirklich du machst zwischen Pointer und Reference nen Unterschied bzgl. des Speeds. Dass man unnötige Kopien vermeidet ist ja klar. :)