Fragen zu mutable und explicit
-
Hallo, ich habe zwei Fragen:
mutableerlaubt es ja, eine Variable selbst in als const deklarierten Funktionen zu verändern.
Ich habe mir Gedanken dazu gemacht und bin auf zwei folgende Anwendungsfälle gekommen:
- Klasseninterner Funktionsaufrufzähler (für Debugging):
class Sample { public: /* ... */ int getVal() const { ++getValCallCounter; return value; } int getNumberOfCalls() const { return getValCallCounter; } private: /* ... */ int value; mutable int getValCallCounter; };- Caching von Ergebnissen:
class Sample { public: /* ... */ double calculate() const { if(cached) return cachedValue; double result; /* berechne irgendwas mit viel Zeitaufwand und deklariere result mit Ergebnis der Rechnung */ cached = true; cachedValue = result; return result; } private: /* ... */ mutable bool cached; mutable double cachedValue; };Nun meine Fragen hierzu:
Kennt ihr noch weitere Anwendungsfälle für mutable?
Sind diese Anwendungsfälle für mutable passend, wenn nein, warum nicht?Nun zur zweiten Frage:
2. Mit
explicitist es ja möglich, einen Konstruktor so zu deklarieren, dass dieser nur aufgerufen werden kann, wenn die Variablentypen der Parameter genau übereinstimmen - ohne implizite Casts.Meine Fragen dazu:
Welche Anwendungsfälle gibt es hier und was ist zu beachten?
Gibt es Empfehlungen eurerseits, einen Konstruktor unter gewissen Umständen immer explicit zu deklarieren?
-
1. mutable:
Das hat durchaus seine Anwendungsfälle und die von dir beschriebenen gehören dazu. Es ist halt für Sachen gedacht, die für die interne Funktion der Klasse nötig sind aber nicht zu den klassischen Datenelementen gehören.
2. explicit
Dies ist vor allem dafür gedacht, um unerwartetes Verhalten durch implizite Casts zu verhindern.
-
Frager schrieb:
Kennt ihr noch weitere Anwendungsfälle für mutable?
Grundsätzlich benötigst du
mutablefür Variablen, die nicht zum logischen Zustand der Instanz nach aussen beitragen. Damit meine ich Variablen, die verändert werden können, sodass das Objekt aus der Sicht des Benutzers trotzdem konstant scheint.Frager schrieb:
Sind diese Anwendungsfälle für mutable passend, wenn nein, warum nicht?
Der Funktionsaufrufzähler scheint mir nicht gerade üblich (
mutableist hier aber trotzdem gerechtfertigt), wohl aber das Caching. Ich verwendemutablehauptsächlich zu diesem Zweck.Frager schrieb:
2. Mit
explicitist es ja möglich, einen Konstruktor so zu deklarieren, dass dieser nur aufgerufen werden kann, wenn die Variablentypen der Parameter genau übereinstimmen - ohne implizite Casts.Nein, das hast du falsch verstanden.
Das Schlüsselwort
explicitbedeutet, dass Konstruktoren, die einen Parameter besitzen (evtl. weitere Defaultparameter), nicht dazu verwendet werden dürfen, implizite Konvertierungen vom Parametertyp zu deinem Klassentyp vorzunehmen.struct MyClass { explicit MyClass(int i); }; void Function(MyClass); int main() { // Konstruktorargument wird implizit konvertiert (long -> int) MyClass Obj(42l); // OK // Funktionsargument wird implizit konvertiert (int -> MyClass) Function(42); // verboten! }Frager schrieb:
Welche Anwendungsfälle gibt es hier und was ist zu beachten?
explicitbenötigst du immer, wenn du nicht willst, dass ein Ausdruck mit Typen des Konstruktorarguments eine Instanz generiert. Im Allgemeinen ist das der Fall, sofern der Parameter nicht ein gleichartiges Objekt repräsentiert, sondern nur Informationen beinhaltet, um die Klasse im Konstruktor zu initialisieren.std::complex<float> c = 3.2f; // kein expliziter Konstruktor // Float-Literal kann implizit in komplexe Zahl umgewandelt werden, // c repräsentiert nach dem Konstruktoraufruf die Zahl 3.2 std::string s = "hallo"; // genau das gleiche mit den Konvertierungen: // const char[6] -> const char* -> std::string (plus evtl. Kopierkonstruktor) std::vector<int> v(15); // expliziter Konstruktor, weil 15 nicht dem Wert des // Containers entspricht, sondern nur ein Informationsträger für die // Initialisierung ist (hier die Initialgrösse)Frager schrieb:
Gibt es Empfehlungen eurerseits, einen Konstruktor unter gewissen Umständen immer explicit zu deklarieren?
Ja, grundsätzlich solltest du dir bei Konstruktoren, welche mit einem Parameter aufgerufen werden können (ausser Kopierkonstruktoren), gut überlegen, ob du implizite Konvertierungen zulassen willst. Lieber einmal mehr
explicithinschreiben, implizite Konvertierungen können eine mühsame Fehlerquelle sein (Stichwort Smart-Pointer).
-
Achso, alles klar.
Vielen Dank für die schnelle Aufklärung!
-
Weitere Anwendungsfälle für mutable:
* Synchronisation Primitives (z.B. Mutexen)
* Lazy-Evaluation (kann man als Spezialfall von Caching ansehen)