In Ausdrücken und Zuweisungen können fundamentale Datentypen
(char, int, float, double, ...) beliebig gemischt werden.
Um die gewünschte Aktion durchführen zu können, müssen die verschiedenen
Werte auf einen gemeinsamen Datentyp gebracht werden. Dazu werden die
Werte, wenn möglich, automatisch so umgewandelt, dass keine
Informationen verloren gehen. Es besteht aber für den Programmierer
durch Angabe des Typs die Möglichkeit, eine Typumwandlung zu erzwingen.
Um dies zu erreichen sollte in C++ die funktionale Typumwandlung genutzt
werden. Dazu wird vor dem, in Klammern stehenden, Objekt der gewünschte
Typ geschrieben:
int i;
float f;
double d;
d = i + f; // i wird zu float, i+f dann zu double.
i = int(f) + int(d); // f, d werden zu int. Nachkommastellen von f und d werden abgeschnittenIn C++ kann derselbe Effekt auch für benutzerdefinierte Typen (Klassen) erreicht werden. Hierbei unterscheidet man zwischen der impliziten (automatischen) und der expliziten (erzwungenen) Umwandlung.
Eine automatische Typumwandlung kann auf zwei Arten ermöglicht werden: Durch Definition eines bestimmten Konstruktors oder eines speziellen Umwandlungsoperators.
Jeder Konstruktor, der mit genau einem Argument aufgerufen werden kann, definiert implizit eine Typumwandlung. Dazu zählen auch Konstruktoren mit mehr als einem Parameter, die Default-Argumente besitzen.
class Bruch {
int p_x = 0, p_y = 1; // Zähler und Nenner
public:
Bruch() = default;
Bruch(int x) // implizite Konvertierung
: p_x(x)
{}
Bruch(int x, int y)
: p_x(x), p_y(y)
{}
Bruch operator*(const Bruch& r)
{
Bruch res;
res.p_x = p_x * r.p_x;
res.p_y = p_y * r.p_y;
return res;
}
};
int main()
{
Bruch x(5, 2);
Bruch y = x * 15; // implizit: Bruch(15)
return 0;
}Zur Umwandlung von int nach Bruch wird der
Konstruktor benutzt, der eine int-Zahl als Parameter
akzeptiert. Die Umwandlung ist so aber nur in diese Richtung möglich.
Der Ausdruck 15 * x ist nicht möglich, da für einen
fundamentalen Datentyp keine Umwandlungsfunktionen definiert werden
können. In diesem Fall müsste man eine Umwandlung selbst durchführen,
z.B. in der Form Bruch(15) * x.
Manchmal können bei dieser Form der Typumwandlung Probleme auftreten.
Man definiert einen entsprechenden Konstruktor, möchte aber keine
automatische Typumwandlung festlegen. Dies kann man verhindern, indem
man dem Konstruktor das Schlüsselwort explicit
voranstellt.
Alternativ zum Konstruktor erfolgt eine automatische Typumwandlung
auch durch die Definition eines entsprechenden Umwandlungsoperators.
Dieser wird definiert durch das Schlüsselwort operator,
gefolgt von dem Typ, in den man umwandeln möchte. Dabei muss es sich
nicht um einen fundamentalen Datentyp handeln.
class Bruch {
// ...
public:
explicit Bruch(int x) // jetzt explizit
: p_x(x)
{}
operator double() const
{
return double(p_x) / double(p_y);
}
};
int main()
{
Bruch x(5, 2);
double y;
y = x * 15; // x->double
y = 10 * x; // hier auch möglich x->double
return 0;
}Hier wird x mittels des Operators von Bruch
nach double umgewandelt. Die Anwendung von
explicit beim Konstruktor ist notwendig, da die Umwandlung
sonst zweideutig ist.
Ähnlich wie bei der automatischen Typumwandlung gibt es auch bei der expliziten Typumwandlung mehr als eine Art. Zum einen die Funktionale Notation, zum anderen die Cast-Notation.
Die Typumwandlung wird durch Aufruf eines entsprechenden Konstruktors erreicht. Dabei kann auch eine Liste von Argumenten übergeben werden:
Bruch y = x * Bruch(25, 11);
Bruch y = x * Bruch(15);Da Typumwandlungen verschiedenen Zwecken dienen können, existieren
zur genauer definierten Typumwandlung vier Operatoren:
static_cast, dynamic_cast, const_cast, reinterpret_cast.
Die beiden gängigsten sind die ersten beiden. Sie werden im Folgenden näher erläutert.
static_cast<typ>(parameter)
Der static_cast-Operator konvertiert den
parameter in einen Typ typ. Dies geschieht
zwischen verwandten Typen, wie etwa verschiedenen Zeigertypen, zwischen
einer Aufzählung und einem integralen Typ oder einem Gleitkommatyp und
einem integralen Typ. Man kann auch Zeiger innerhalb einer
Klassenhierarchie bewegen, d.h. die Umwandlung von einer Basisklasse in
eine abgeleitete Klasse (Downcast) oder umgekehrt (Upcast). Im Gegensatz
zum dynamic_cast findet keine Überprüfung zur Laufzeit
statt, womit der Programmierer für die Richtigkeit der Umwandlung
verantwortlich ist.
class B {/*...*/};
class D : public B {/*...*/};
void f(B* pb, D* pd) {
D* pd2 = static_cast<D*> (pb);
// unsicher, pb zeigt evtl. nicht auf D sondern auf B
B* pb2 = static_cast<B*> (pd); //sicher
}dynamic_cast<typ>(parameter)
Der dynamic_cast-Operator konvertiert den
parameter in einen Typ typ. typ
muss ein Zeiger oder eine Referenz auf eine zuvor definierte Klasse
sein. Als parameter wird entsprechend ein Zeiger oder ein
Objekt einer Klasse übergeben. Der Zweck des dynamic_cast
ist die Behandlung von Fällen, in denen die Korrektheit der Umwandlung
nicht durch den Compiler ermittelt werden kann. Der Operator schaut
dabei zur Laufzeit auf den Typ des zu wandelnden Objekts
(parameter) und entscheidet, ob die Umwandlung sinnvoll
ist. Falls nicht, wird bei Zeigern ein nullptr
zurückgegeben und bei Referenzen eine bad_cast-Exception
geworfen.
class B {/*...*/};
class D : public B {/*...*/};
class E : public B {/*...*/};
void f() {
D d;
B& b_ref = d;
D* d_ptr = dynamic_cast<D*>(&b_ref); // OK, b_ref ist Instanz von D.
D& d_ref = dynamic_cast<D&>(b_ref); // OK, analog.
E* e_ptr = dynamic_cast<E*>(&b_ref); // e_ptr ist nullptr. b_ref ist keine Instanz von E.
E& e_ref = dynamic_cast<E&>(b_ref); // Exception. b_ref ist keine Instanz von E.
}Für das Casting von shared_ptr gibt es entsprechende
Cast-Operatoren:
shared_ptr<T> static_pointer_cast (const shared_ptr<U>& sp)
undshared_ptr<T> dynamic_pointer_cast (const shared_ptr<U>& sp)Diese liefern jeweils eine Kopie des übergebenen Parameters mit
denselben Bedingungen und Ergebnissen wie für normale Pointer
beschrieben. Es handelt sich dabei im Prinzip nur um eine
Kurzschreibweise für z.B.
static_cast<T*>(sp.get()).