Referenz auf Klassenattribut zurückliefern
-
Man sollte niemals Membervariablen als Referenz zurück geben. Siehe Scott Meyers "Effektiv C++ Programmieren". Da dieses Vorgehen die Kapselung ad absurdum führt! Kannste gleich das Member als public deklarieren, wenn du das machst. Members gibt man nur in äußersten bestimmten Fällen als Ref zurück, z.B. wenn es sich bei der Klasse um einen Container handelt.
Übrigens, jeder gute Compiler kann Kopien von Objekten weg optimieren! Also, keine Referenz zurück geben!
Und lokale Variablen, gibt man auch nicht als Referenz zurück. Dürfte auch jeder vernünftige Compiler eine Warning ausspucken.
Einfach mal im Netz oder hier im Forum nach RVO suchen.
-
Artchi schrieb:
Man sollte niemals Membervariablen als Referenz zurück geben.
Das würde ich nicht auf Const-Referenzen verallgemeinern (ich bin mir nicht sicher, ob du dich nur auf Referenzen auf unqualifizierte Objekte bezogen hast).
Auch mit der Optimierung ist das nicht immer so einfach. Die Chancen auf RVO stehen gut, wenn es sich um funktions-lokale Variablen handelt - bei Membervariablen hingegen weniger.
-
Nexus schrieb:
Artchi schrieb:
Man sollte niemals Membervariablen als Referenz zurück geben.
Das würde ich nicht auf Const-Referenzen verallgemeinern (ich bin mir nicht sicher, ob du dich nur auf Referenzen auf unqualifizierte Objekte bezogen hast).
Hmm. Ich schon.

Wenn die Referenzconstist, dann kannst du zwar den Inhalt nicht verändern, aber du hast immer noch Zugriff auf etwas, was eigentlich privat ist. Was ist, wenn jetzt eine Member des Objektes halt nicht genau so zurückgegeben werden soll, wie sie drin steht? - Also z.B noch ein Offset oder so dazu kommt. Das hast du mit einem getter voll unter Kontrolle, aber mit einer zurückgegeben Referenz nicht. Da müsstest du dann im Nachhinein das zurück gegebene Objekt nur noch als Proxy benutzen, und das eigentliche Objekt, das es mal war anderst benennen und dann das eigentliche, sowie das Proxy Objekt halten, welches dann auch immer die aktuellen Daten halten muss und kann sie nicht erst berechnen, wenn sie wirklich abgefragt wird.Meine Meinung zu Kapselung.

-
drakon schrieb:
Was ist, wenn jetzt eine Member des Objektes halt nicht genau so zurückgegeben werden soll, wie sie drin steht? - Also z.B noch ein Offset oder so dazu kommt. Das hast du mit einem getter voll unter Kontrolle, aber mit einer zurückgegeben Referenz nicht.
Aber es geht doch gerade um den Rückgabetyp eines getters?
mytype const& get_member_ref(unsigned offset) const { return myData[offset]; }So kann der Aufrufer entscheiden, ob er eine Kopie will, auf die er dann auch schreiben kann, oder eine Nur-Lesen-Referenz, wo garantiert nichts kopiert wird.
Oder verstehe ich dich falsch?
-
Aber was ist jetzt, wenn du nicht wirklich den Wert von myData[offset] zurückgeben willst, sondern halt noch ein wenig mehr (optimierung oder was auch immer..)
Dann musst du so etwas machen:
mytype const& foo::get_member_ref(unsigned offset) const { mytype temp = myData[offset]; temp.x + 0.1f; // oder sonst was return temp; }Was ja offensichtilch böse ist. Also so etwas:
class foo { ... private: mytype myData[COUNT]; mytype myData_offsets[COUNT]; }; mytype const& get_member_ref(unsigned offset) const { return myData_offsets[offset]; }Und hier darfst du, soblad sich myData ändert auch noch schauen, dass myData_offsets auch aktuell ist.
Und, wie du selber schon vermutest, ist das nicht so das wahre.

Es gibt natürlich Ausnahmen, wo sowas Sinn macht, aber nicht wenn man es vermeiden kann. (siehe z.Bstd::string::c_str()).
-
Jetzt verstehe ich, was du meinst.
In dem Fall würde man das Interface natürlich ändern. Macht aber nichts, weil man eine Referenz an den Rückgabewert einer Funktion binden kann und diese Referenz im aufrufenden Scope gültig bleibt.
mytype foo::get_data(unsigned offset) const // interface wurde geändert, vorher wars eine const-ref, jetzt ist es eine kopie { mytype temp = myData[offset]; temp.x + 0.1f; return temp; } int main() { mytype const& data = bar.get_data(42); // aufruf hat sich trotz interface-änderung nicht geändert std::cout << data; // gültig }AFAIK ist das erlaubt!?
Ich würde aber Referenzen sowieso nur dann zurückliefern, wenn ich solche Änderungen sicher ausschließen kann.
-
Ja. Das darf man machen, allerdings könnte ja ein Schlaumeier kommen und das hier machen:
int main() { mytype const* data = &(bar.get_data(42)); std::cout << *data; // hups }Natürlich kann man hier jetzt argumentieren, dass der User dann selbst Schuld ist, aber man sollte dieses Riskiko nur eingehen, wenn man es wirklich braucht. Und das ist nicht der Fall, wenn man Angst vor Kopien hat.
-
Ja, grundsätzlich stimmt das schon. Ich setze eigentlich auch eher Kopien ein, bei kleinen Objekten wie 2D-/3D-Vektoren etc. sowieso. Der Vorteil von Kopien ist, dass weniger potentielle Fehlerquellen (Referenz auf totes Objekt, ...) vorhanden sind und man einheitlich bleiben kann, falls mal etwas verändert werden soll. Aber wenn ich eine Klasse habe, die gewisse zusammengehörige Eigenschaften in einem Typ speichert, finde ich es okay, diesen als Const-Referenz zurückzugeben. Ein konkretes Beispiel bei mir sind Statusdaten einer Spielfigur in einem Spiel (Anzahl Leben, Punkte, gesammelte Gegenstände und solche Sachen). Da sehe ich keinen Sinn darin, eine unnötige Kopie anzulegen. Denn wenn grössere Dinge wie Container enthalten sind und man die Funktion oft braucht, kann das von der Performance her schon etwas ausmachen. Und ich gewinne im Normalfall nichts durch die Kopie.
Ich zumindest versuche Getter-Funktionen - egal welchen Rückgabetyps - möglichst allgemein anzuwenden. Also nicht etwa die Const-Referenz lange zwischenzuspeichern oder so. Meist benutze ich den zurückgegebenen Wert gleich in einem temporären Ausdruck, damit er sicher noch existiert, oder ich erstelle eine lokale Kopie, um die Lebensdauer zu verlängern. Eine Kopie kann man ja immer noch erstellen, aber man wird nicht dazu gezwungen, wenns nicht nötig ist. Wenn ich selber etwas schreibe, erwarte vom Benutzer aber auch, dass er nicht irgendwelche Annahmen bezüglich Lebensdauer oder Gültigkeit trifft, wenn eine Referenz im Spiel ist. Im Zweifelsfalle sollte man sowieso die Dokumentation anschauen. Und solange man Const-Referenz-Rückgabetypen keine besondere Behandlung zukommen lässt, ist es auch kein Problem, die Schnittstelle falls nötig doch zu einer Kopie zu ändern (nur rekompilieren). Allerdings habe ich bisher fast nie solche Änderungen durchführen müssen.
Ein ganz anderes Argument: Bei einigen Typen ist eine Kopie aus semantischen Gründen nicht angebracht - vielleicht, weil sie gewisse Seiteneffekte mit sich bringt - oder weil das Objekt schlicht und einfach nicht kopierbar ist.
Also zusammengefasst: Ich bin nicht der Meinung, dass Const-Referenzen die Kapselung grundsätzlich verletzen. Ich wäge den konkreten Fall ab; dabei spielt ein Rolle, wie gross das zurückgegebene Objekt ist, wer den Code benutzt oder wie wichtig Geschwindigkeit jeweils ist.
-
Ich benutze sie ja auch. Ich habe nur dagegen argumentiert, dass es die Kapselung nicht beeinträchtigt. Es gibt viele gute Gründe, dass man eben doch const Referenzen benutzt und wenn man davon ausgeht, dass der Benutzer (oftmals ja einer selbst) sich der Probleme bewusst ist, dann ist es auch nicht so tragisch.
-
Du hast völlig Recht. Ich wollte ursprünglich nur Artchis Aussage, man solle nie Referenzen zurückgeben, etwas relativieren. Vielleicht hab ich das dann später etwas zu sehr auf die Kapselung bezogen.