Typumwandlung

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 abgeschnitten

In C++ kann derselbe Effekt auch für benutzerdefinierte Typen (Klassen) erreicht werden. Hierbei unterscheidet man zwischen der impliziten (automatischen) und der expliziten (erzwungenen) Umwandlung.

Implizite (automatische) Typumwandlung

Eine automatische Typumwandlung kann auf zwei Arten ermöglicht werden: Durch Definition eines bestimmten Konstruktors oder eines speziellen Umwandlungsoperators.

Konstruktor zur Typumwandlung

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.

Typumwandlung durch Operator

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.

Explizite (erzwungene) Typumwandlung

Ä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.

Funktionale 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);

Cast-Notation

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.

        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
        }
        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.
        }