Konstante Getter-Funktionen?
-
Guten Abend,
ich habe mir angewöhnt, in meinen Klassen Getter-Funktionen als konstant zu definieren:
class A { int a_; public: const int a() { return a_; } };Nun compilierte ich eines meiner Programme mal mit
-Wextraund sah, dass für diese Fälle Warnungen ausgegeben werden, in Scott Meyers "Effektiv C++ programmieren" steht jedoch, man solle konstante Getter vorziehen.Wo liegt nun aber der Fehler? Welches ist das bessere Design und sollte aus welchen Gründen vorgezogen werden?
-
also ich kenne auch nur den tipp mit dem const... vlt interpretierst du die Fehlermeldung deines Compilers falsch...
-
int a() const { return a_; }Bei dir ist der zurück gegebene Wert konstant (was reichlich sinnlos ist, daher die Warnung), hier ist a() konstant, wodurch a() auch auf konstante A angewendet werden kann, da dieses A garantiert nicht verändert werden kann (Ausnahmen durch mutable mal ausgenommen).
-
SeppJ schrieb:
int a() const { return a_; }Bei dir ist der zurück gegebene Wert konstant (was reichlich sinnlos ist, daher die Warnung), hier ist a() konstant, wodurch a() auch auf konstante A angewendet werden kann, da dieses A garantiert nicht verändert werden kann (Ausnahmen durch mutable mal ausgenommen).
Tut mir Leid, ich habe mich wirklich selten dämlich ausgedrückt.

Ich meinte natürlich Getter mit konstantem Rückgabewert und keine konstanten Funktionen.
-
Clundsch schrieb:
also ich kenne auch nur den tipp mit dem const... vlt interpretierst du die Fehlermeldung deines Compilers falsch...
Warnung: Typkennzeichner an Funktions-Rückgabewert ignoriert [-Wignored-qualifiers]-Wignored-qualifiers ist laut Erklärungen genau für solche Fälle mit
constgedacht.
-
also bei mir funktioniert
int const a() {}und
const int a() {}
-
[c]
ichnicht schrieb:
Ich meinte natürlich Getter mit konstantem Rückgabewert und keine konstanten Funktionen.
Das meinst du vielleicht, aber nicht Scott Meyers. Der meint konstante Memberfunktionen in seinem Tipp 21 (zweite Ausgabe, die Nummern wurden später durcheinander gewürfelt).
Clundsch schrieb:
also bei mir funktioniert
int const a() {}und
const int a() {}Das ist auch beides exakt das gleiche und beides nicht sehr sinnvoll, was meine Ahnung im anderen Thread bestätigt, dass du const nicht wirklich verstanden hast. Ist mir jetzt aber zu spät, um noch mal groß auszuholen. Du wirst dich gedulden müssen, bis jemand anderes es dir erklärt oder bis ich morgen an diesen Thread denke (sofern ich überhaupt dran denke, was nicht unbedingt der Fall ist :-\ )
-
SeppJ schrieb:
[c]
ichnicht schrieb:
Ich meinte natürlich Getter mit konstantem Rückgabewert und keine konstanten Funktionen.
Das meinst du vielleicht, aber nicht Scott Meyers. Der meint konstante Memberfunktionen in seinem Tipp 21 (zweite Ausgabe, die Nummern wurden später durcheinander gewürfelt).
In 55 specific ways to improve your programs and designs, Tipp 3 schreibt er das schon. Sein Beispiel:
Scott Meyers schrieb:
const Rational operator*(const Rational& lhs, const Rational& rhs);
Das soll verhindern dass jemand sowas schreibt:
(a * b) = c;
-
Meyers erwähnt das im Zusammenhang mit überladenen Operatoren die "user defined types" (=eigene Klassen) als Returntyp haben. (Bei Gettern macht es zugegebenerweise allerdings genau so Sinn, vorausgesetzt sie getten einen UDT).
Einem int Returntyp ein top-level const umzubinden ist wohl höchst sinnfrei.Dazu zwei doofe Fragen...
- Darf eine T&& Referenz wirklich an einen Returnwert vom Typ "T const" gebunden werden? (Spricht: funktioniert move noch wenn man den Returnwert top-level const macht?)
- Wie ist "this" bei den Default Copy- und Move-Assignment Operatoren qualifiziert - "&" oder "&&"?
Wenn (1) = ja, dann ist das top-level const wohl zumindest nicht schädlich.
Wenn (2) = "&", dann ist es allerdings (C++11 konformen Compiler und passend definierte Klasse vorausgesetzt) nicht nötig.
-
Jetzt bin ich verwirrt und weiß erst recht nicht mehr, wann eine Funktion einen konstanten und wann sie keinen konstanten Rückgabewert haben sollte.
-
Wenn du dich nicht mit C++11 Feinheiten rumschlagen willst, UND diverse (mMn. sehr weit hergeholte) Programmierfehler (siehe Mechanics' Beitrag) verhindern willst, dann wenn
- Die Funktion einen Wert "by value" zurückgibt
UND - Der Returnwert einen "user defined type" hat (user defined type = class/struct)
Natürlich gibt es ein paar Ausnahmen, aber wenn du denen begegnest stolperst du eh von selbst drüber, weil etwas nicht mehr geht was ganz klar gehen sollte.
Heisst bei
int,float,charetc. sowie sämtlichen Zeigern macht ein top-level const keinen Sinn. Es schadet nicht, aber es bringt auch nix. Weil nämlich C++ von sich aus schon die besagten weit hergeholten Programmierfehler verhindert. Wenn du sehr auf Konsistenz Wert legst kannst du es natürlich auch gerne bei solchen Typen schreiben. Sonst würde ich sagen lass es.Und zum Thema Zeiger noch um einem möglichen Misverständnis vorzugreifen:
MeineKlasse* const MeineFunktion(); // <-- DAS ist ein top-level const (Zeiger ist const) MeineKlasse const* MeineFunktion(); // <-- das ist KEIN top-level const (MeineKlasse Objekt ist const) const MeineKlasse* MeineFunktion(); // <-- das ist AUCH KEIN top-level const, ist das selbe wie MeineKlasse const*
- Die Funktion einen Wert "by value" zurückgibt
-
SeppJ schrieb:
Clundsch schrieb:
also bei mir funktioniert
int const a() {}und
const int a() {}Das ist auch beides exakt das gleiche
Kleine Anmerkung, das ist nicht ganz exakt das Gleiche, sondern unterschiedliche Funktionen, deren Typ sich unterscheidet. Die Eleminierung von top-level cv-Qualikationenen, ebenso wie die Substitution von Array- durch Zeigerdeklarationen bzw. von Funktionen durch Funktionszeiger findet nur für die Parameter statt.
Funktionen, die Arrays oder Funktionen zurückgeben gibt es nicht, solche Deklarationen sind ill-formed. Wer Spaß daran hat, kann aber gerne seine void-Funktionen durch const volatile void-Funktionen ersetzen.
Diese cv-Qualifikation ändert allerdings nichts am Typ eines Ausdrucks, der einen Funktionsaufruf darstellt, wenn der Rückgabetyp ein Skalar ist.- Darf eine T&& Referenz wirklich an einen Returnwert vom Typ "T const" gebunden werden? (Spricht: funktioniert move noch wenn man den Returnwert top-level const macht?)
nein. Etwas anderes gilt nat. für deduzierbare Templateparameter, ein T&& kann auch als T=const U deduziert werden. Der gewöhnliche Move-Konstruktor/Zuweisungsoperator nimmt aber nur modifizierbare rvalues. in jedem Fall kommt es auf den Typ des Ausdrucks an, der rechts steht, und der ist für Skalare stets unqualifiziert.
-
camper schrieb:
- Darf eine T&& Referenz wirklich an einen Returnwert vom Typ "T const" gebunden werden? (Spricht: funktioniert move noch wenn man den Returnwert top-level const macht?)
nein. Etwas anderes gilt nat. für deduzierbare Templateparameter, ein T&& kann auch als T=const U deduziert werden. Der gewöhnliche Move-Konstruktor/Zuweisungsoperator nimmt aber nur modifizierbare rvalues. in jedem Fall kommt es auf den Typ des Ausdrucks an, der rechts steht, und der ist für Skalare stets unqualifiziert.
OK, Skalare mal beiseite...
Wenn ein Move-Konstruktor nicht mit einer "const UDT fun();" funktioniert, dann ist
a) Clang 3.0 verbuggt (MSVC auch, aber OK, ... QEE)
und
b) top-level const im Returntyp mit UDTs BÖSE weil es Move verhindertOder übersehe ich da jetzt was (bezüglich (b)).
ps: ich verstehe deine Antwort bezüglich "const int a()" vs "int const a()" nicht. Ob const vor oder hinter dem int ist doch egal, ist doch in beiden Fällen top level const... nicht?
-
hustbaer schrieb:
Wenn ein Move-Konstruktor nicht mit einer "const UDT fun();" funktioniert, dann ist
a) Clang 3.0 verbuggt (MSVC auch, aber OK, ... QEE)Nein, das ist schon richtig so. Ein Move Konstruktor nimmt normalerweise eine UDT&& und keine const UDT&&. Es ist ja normalerweise gerade erst der Zweck eines Move Konstruktors, das Objekt, von dem gemoved wird, zu ändern...
hustbaer schrieb:
b) top-level const im Returntyp mit UDTs BÖSE weil es Move verhindert
top-level const im Returntyp ist nicht prinzipiell böse, aber sowas wie const int als Return Type ist wohl sinnlos. Die einzig mir bekannte sinnvolle Anwendung von top-level const auf einem Returntype, wenn ein Objekt by Value returned wird, ist operator overloading.
hustbaer schrieb:
ps: ich verstehe deine Antwort bezüglich "const int a()" vs "int const a()" nicht. Ob const vor oder hinter dem int ist doch egal, ist doch in beiden Fällen top level const... nicht?
Ich glaub, da hat er einfach einmal das const übersehen

-
dot schrieb:
Ich glaub, da hat er einfach einmal das const übersehen

Err ja, hatte da sowas wie
int a(); const int a();gelesen.
-
hustbaer schrieb:
Wenn du dich nicht mit C++11 Feinheiten rumschlagen willst, UND diverse (mMn. sehr weit hergeholte) Programmierfehler (siehe Mechanics' Beitrag) verhindern willst, dann wenn
- Die Funktion einen Wert "by value" zurückgibt
UND - Der Returnwert einen "user defined type" hat (user defined type = class/struct)
Natürlich gibt es ein paar Ausnahmen, aber wenn du denen begegnest stolperst du eh von selbst drüber, weil etwas nicht mehr geht was ganz klar gehen sollte.
Heisst bei
int,float,charetc. sowie sämtlichen Zeigern macht ein top-level const keinen Sinn. Es schadet nicht, aber es bringt auch nix. Weil nämlich C++ von sich aus schon die besagten weit hergeholten Programmierfehler verhindert. Wenn du sehr auf Konsistenz Wert legst kannst du es natürlich auch gerne bei solchen Typen schreiben. Sonst würde ich sagen lass es.Und wenn ich mich mit C++11 Feinheiten herumschlagen will?

- Die Funktion einen Wert "by value" zurückgibt
-
SeppJ schrieb:
int a() const { return a_; }Bei dir ist der zurück gegebene Wert konstant (was reichlich sinnlos ist, daher die Warnung), hier ist a() konstant, wodurch a() auch auf konstante A angewendet werden kann, da dieses A garantiert nicht verändert werden kann (Ausnahmen durch mutable mal ausgenommen).
Das stimmt so, aber das Hauptziel hier ist wohl eher anzuzeigen, dass das Objekt selber bei der Ausführung von a() nicht verändert wird, was bei einem getter ja wohl immer der Fall sein sollte. Oder anders: alle Methoden, die das Objekt (sprich darauf wo this zeigt) nicht verändern, sollten als const deklariert werden. Dann dürfen allerdings auch nur aus dieser Methode heraus als const deklarierte Methoden aufgerufen werden. Das überprüft dann der Compiler für dich.
-
dot schrieb:
hustbaer schrieb:
Wenn ein Move-Konstruktor nicht mit einer "const UDT fun();" funktioniert, dann ist
a) Clang 3.0 verbuggt (MSVC auch, aber OK, ... QEE)Nein, das ist schon richtig so. Ein Move Konstruktor nimmt normalerweise eine UDT&& und keine const UDT&&. Es ist ja normalerweise gerade erst der Zweck eines Move Konstruktors, das Objekt, von dem gemoved wird, zu ändern...
Clang 3.0 bindet einen
const UDTReturnwert direkt anUDT&&Parameter (UDT= konkreter Typ, kein Template-Parameter!).
Nach dem was camper geschrieben hat dürfte er das ja nicht. Oder doch?Also konkret, im Beispiel unten macht Clang 3.0 2x move und nicht 1x move und 1x copy.
Es sei denn ich hatte in meinem Test-Code nen Fehler -- muss das heute Abend nochmal genauer untersuchen.dot schrieb:
hustbaer schrieb:
b) top-level const im Returntyp mit UDTs BÖSE weil es Move verhindert
top-level const im Returntyp ist nicht prinzipiell böse, aber sowas wie const int als Return Type ist wohl sinnlos. Die einzig mir bekannte sinnvolle Anwendung von top-level const auf einem Returntype, wenn ein Objekt by Value returned wird, ist operator overloading.
Also wenn ich euch richtig verstanden habe, dann...
std::string good(); std::string const bad(); std::string pipe(std::string s) { return s; } void consumer(std::string s) { /* konsumiert irgendwie s - ist ja egal wie */ } void test() { consumer(pipe(good())); // 1x move consumer(pipe(bad())); // 1x copy }...und das ist für mich böse, weil ... wieso move verhindern?
Wenn man
good() = "foo";oder ähnliches verhindern will ohne move zu verhindern, dann mussstd::stringseinenoperator =doch bloss "&" qualifizieren:template <...> class basic_string { // ... basic_string& operator = (basic_string other) &; // tadaa };const im Returntyp ist dabei nicht nötig.
Daher auch die Frage ob die Signatur eines compilergenerierten operator = "&" oder "&&" qualifiziert ist.
dot schrieb:
hustbaer schrieb:
ps: ich verstehe deine Antwort bezüglich "const int a()" vs "int const a()" nicht. Ob const vor oder hinter dem int ist doch egal, ist doch in beiden Fällen top level const... nicht?
Ich glaub, da hat er einfach einmal das const übersehen

Nachdem camper SO selten Fehler macht dachte ich mir ich frag' sicherheitshalber lieber

-
ichnicht schrieb:
hustbaer schrieb:
Wenn du dich nicht mit C++11 Feinheiten rumschlagen willst, UND diverse (mMn. sehr weit hergeholte) Programmierfehler (siehe Mechanics' Beitrag) verhindern willst, dann wenn
- Die Funktion einen Wert "by value" zurückgibt
UND - Der Returnwert einen "user defined type" hat (user defined type = class/struct)
Natürlich gibt es ein paar Ausnahmen, aber wenn du denen begegnest stolperst du eh von selbst drüber, weil etwas nicht mehr geht was ganz klar gehen sollte.
Heisst bei
int,float,charetc. sowie sämtlichen Zeigern macht ein top-level const keinen Sinn. Es schadet nicht, aber es bringt auch nix. Weil nämlich C++ von sich aus schon die besagten weit hergeholten Programmierfehler verhindert. Wenn du sehr auf Konsistenz Wert legst kannst du es natürlich auch gerne bei solchen Typen schreiben. Sonst würde ich sagen lass es.Und wenn ich mich mit C++11 Feinheiten herumschlagen will?

Dann schreib lieber deine Klassen so, dass der Assignment-Operator nicht auf Rvalues funktioniert (siehe Beispiel mit basic_string oben).
Das entspricht mMn. nämlich am ehesten dem was eingebaute Typen machen.
- Die Funktion einen Wert "by value" zurückgibt
-
Ich hab grade mal mit r-values gespielt und das Beispiel von oben etwas verändert. Das Ergebnis verwundert mich:
#include <iostream> #include <cstring> using namespace std; struct X { X(char const* str) : ptr(0) {ptr = new char[strlen(str)+1]; strcpy(ptr, str);} X(X&& other) : ptr(other.ptr) {other.ptr = 0; cout << "move " << ptr << ' ';} X(X const& other) : ptr(0) {ptr = new char[strlen(other.ptr)+1]; strcpy(ptr, other.ptr); cout << "copy " << ptr << ' ';} ~X() {delete ptr;} char* ptr; }; X good() {X x("good"); return x;} X const bad() {X x("bad"); return x;} X pipe(X x) { return x; } void consumer(X x) { std::cout << "consume " << x.ptr << '\n'; } int main() { consumer(good()); //erwartet: max. 1 move consumer(bad()); //erwartet: max. 1 move consumer(pipe(good()));//erwartet: max. 2 move consumer(pipe(bad())); //erwartet: max. 1 move, 1 copy }Ergebnis gcc-4.5.1: (ideone)
consume good consume bad copy good consume good copy bad consume badwieso kopiert er den "good"? sollte doch perfekt movebar sein? Dass es überall ein move weniger ist dürfte (N)RVO sein (habs auch mit unbenannten RV's in good() und bad() ausprobiert.
Ergebnis VS10 (debug):
move good consume good move bad consume bad move good move good consume good move bad move bad consume badoffenbar bindet der schamlos rrefs an const r-values. Was sagt der Standard dazu?