Automatisch umgewandelt
Unser Optional vom letzten Mal fühlt sich irgendwie noch etwas unrund an. Wir können ihn nicht wirklich als einfachen Ersatz für den Basisdatentyp verwenden, obwohl ja eigentlich das genau das Ziel ist. Daher bauen wir das Template diesmal noch ein wenig um, damit die üblichen Dinge wie Zuweisung vom Basisdatentyp etc. funktionieren. C++ bietet mit Operatorüberladung und automatischen Konvertierungsfunktionen dafür praktische Hilfsmittel.
Video
Code
|
|
Erklärung
Diesmal spielen zwei unterschiedliche Techniken zusammen, um uns unsere gewünschte Funktionalität zu liefern. Wir überladen
einerseits den Operator =
um eine Zuweisung vom int aus zu ermöglichen und bieten andererseits zwei
Konvertierungsfunktionen an um aus einem int
in ein Optional
und umgekehrt umzuwandeln. Streng genommen ist nicht
unbedingt beides notwendig, aber es passt hier eben zusammen.
Der erste Schritt ist die Operatorüberladung für =
. Operatoren in C++ sind grundsätzlich auch nur Funktionen, deren Name
mit operator
beginnt und mit dem eigentlichen Operator endet. Je nach Art des Operators nehmen sie keinen, einen oder zwei
Parameter. In unserem Fall hier der operator=
nimmt als Parameter seine rechte Seite (also die Quelle der Zuweisung) und
gehört zu seiner linken Seite (das Ziel der Zuweisung). Der Operator fungiert hier im Prinzip nur als Alias für den Setter,
von daher ist der eigentliche Code nicht so wahnsinnig umfangreich.
Die zweite Technik, die die Klasse verwendet sind Konvertierungsfunktionen. Konvertierungsfunktionen sind spezielle
Klassenmember, die vom Compiler aufgerufen werden können, wenn ein Typ benötigt wird, aber ein anderer zur Verfügung steht.
Die beiden Funktionen hier im Quelltext stehen in Zeile 20 und 49. Der Konstruktor mit einem Parameter fungiert als
automatische Konvertierung von dem angegebenen Typ (in unserem Fall der unterliegende Typ des Optional
) zur Klasse. Wenn
also ein Optional benötigt wird (bspw. als Parameter der Funktion test2
), aber nur ein int
zur Verfügung steht (siehe
der Aufruf in Zeile 84), dann wird vom Compiler automatisch der Konvertierungskonstruktor verwendet. Aus test2(5)
wird
also test2(Optional<int>(5))
. Die Gegenrichtung ist genauso möglich. Wir haben einen Optional
zur Verfügung, wollen aber
einen einfachen int
verwenden. Zu diesem Zweck gibt es die Konvertierungsfunktion in Zeile 49. Die Schreibweise ist etwas
ungewöhnlich (scheinbar kein Rückgabetyp), aber das ist Absicht: der Rückgabetyp steckt im “Namen” der Funktion (in
Wirklichkeit ist das der Rückgabetyp und die Funktion hat in dem Sinne keinen Namen, da sie nicht direkt aufgerufen werden
kann). Immer dann, wenn wir (in unserem Optional<int>
-Beispiel) einen int brauchen, aber nur einen Optional
zur
Verfügung haben (bspw. in den Zeilen 80 und 83) wird automatisch diese Funktion aufgerufen. Aus test(i)
in Zeile 83 wird
also test(i.int())
(ACHTUNG: kein gültiger C++-Code. Die Konvertierungsfunktion wird nicht so aufgerufen, sondern
automatisch vom Compiler eingesetzt!).
Ich hatte eingangs erwähnt, dass nicht zwingend beide (die Operatorüberladung und die Konvertierungsfunktionen) notwendig
sind. Wir könnten hier im Beispiel auf auf die Überladung von operator=
verzichten. Aus dem i = 10
in Zeile 77 würde der
Compiler dann folgendes machen: i.operator=(Optional<int>(10))
. Während in unserem Beispiel hier direkt die überladene
Variante von =
aufgerufen wird, würde in dem Fall durch den Compiler festgestellt, dass es keine Variante mit int
als
Parameter gibt. Daraufhin würde er versuchen, den int passend zu konvertieren, was ihm über den Konvertierungskonstruktor
gelingt. Zuguterletzt könnte dann die automatisch immer vorhandene Variante des Zuweisungsoperators vom eigenen Typ
angewandt werden. Wenn das so geht, wieso gibt es dann überhaupt beide Möglichkeiten? Erstens ist das Thema
Operatorüberladung wesentlich komplexer, als hier dargestellt und zweitens kann man automatische Konvertierungen
ausschließen wollen (da sie an vielen Stellen verwendet werden, die man vielleicht nicht immer mag), aber trotzdem explizit
die Zuweisung ermöglichen (was dann über den überladenen Operator immernoch geht).
Sowohl die Konvertierungsfunktionen, als auch der Zuweisungsoperator können im Übrigen auch mehrfach überladen sein. So
könnte es zum Beispiel Konstruktoren geben, die von int
oder std::string
umwandeln (wenn das semantisch Sinn ergibt). Der Compiler setzt dann das für die Verwendungsstelle passende ein.