C++-Programmierung/ Nützliches/ Casts

Aus Wikibooks


Es gibt Fälle, in denen man einen Datentyp hat, aber für eine Operation einen etwas anderen Datentyp benötigt. Um diese Umwandlungen zwischen kompatiblen Datentypen zu realisieren, gibt es in C++ vier Cast-Operatoren (cast = gießen, umgießen (Metallurgie), also etwas von gleichem Volumen in eine andere Form bringen). Was „kompatibel“ bedeutet, ist vom jeweiligen Operator abhängig. Die C++-Cast-Operatoren haben relativ lange Namen, was den wesentlichen Vorteil hat, dass sie beim Lesen des Quellcodes sofort ins Auge fallen.

static_cast[Bearbeiten]

Der static_cast-Operator ist der bekannteste und am häufigsten verwendete. Er wandelt Datentypen ineinander um, für die eine Konvertierungsregel existiert. Eine solche Regel kann beispielsweise ein Konstruktor sein, der ein Objekt der gewünschten Klasse aus einem Objekt der übergebenen Variable erstellt. Auch ein überladener Castoperator innerhalb der vorhandenen Klasse kann von static_cast zur Typenkonvertierung herangezogen werden, wenn kein kompatibler Konstruktor vorhanden ist. Außerdem kann mittels static_cast Konstantheit zu einem Objekt hinzugefügt werden, wann dies sinnvoll sein kann, werden Sie in Kürze noch erfahren.

Die elementaren Datentypen können weitgehend ineinander umgewandelt werden. Hier zeigt sich aber auch deutlich der größte Nachteil einer Typumwandlung: Sie geht fast immer mit Datenverlusten einher. Wenn Sie beispielsweise eine Gleitkommazahl in eine Ganzzahl umwandeln, gehen alle Nachkommastellen verloren. Umgekehrt geht bei der Umwandlung von Ganzzahl fast immer Genauigkeit verloren. Dieser Datenverlust findet oft auch bei der Umwandlung von Klassenobjekten statt. Daher sollten Sie sich genau überlegen, ob eine Typumwandlung das ist, was Sie benötigen. Ein weiterer Nachteil einer Typumwandlung ist natürlich, dass es eine gewisse Zeit benötigt, sie durchzuführen.

Syntax:
static_cast<«Zieldatentyp»>(«Quellobjekt»)
«Nicht-C++-Code», »optional«

Eine typische Umwandlung für den static_cast-Operator ist die Durchführung einer Division mit Gleitkommawerten, wenn die Quellvariablen ganzzahlig sind.

#include <iostream>

int main(){
    int a = 7, b = 4;
    double c = static_cast< double >(a) / b;
}

Es genügt eine der Variablen vor der Division nach double zu konvertieren, um die Division selbst als double auszuführen.

const_cast[Bearbeiten]

Mit Hilfe des const_cast-Operators kann die Konstantheit eines Objekts aufgehoben werden. Beachten Sie, dass ein Objekt, dessen Konstantheit mittels const_cast aufgehoben wurde, dennoch nicht verändert werden sollte. Bei älterem Programmcode kann es passieren, dass eine Funktion einen Zeiger auf ein nicht-konstantes Objekt erwartet, obwohl die Funktion das Objekt nicht verändert. Wenn Sie nicht die Möglichkeit haben, den Prototyp der Funktion zu korrigieren, können Sie dem Compiler mittels const_cast mitteilen, dass er das konstante Objekt dennoch an die Funktion übergeben soll.

Die Kompatibilität mit veraltetem oder schlecht geschriebenem Code ist aber nicht die einzige Anwendung für const_cast. In Klassen ist es immer möglich eine Funktion sowohl in einer konstanten, als auch in einer nicht-konstanten Version zu deklarieren. Wenn eine solche Funktion nun in irgendeiner Weise eine Referenz auf ein Objekt innerhalb der Klasse zurück gibt, wobei sich die konstante Funktion nur dahingehend unterscheidet, dass eine Referenz auf ein konstantes Objekt zurückgegeben wird, heißt das, dass Sie in beiden Funktionen den gleichen Inhalt haben. Es findet also eine Codeverdopplung statt, und die ist bekanntermaßen zu vermeiden, wenn dies nur irgendwie möglich ist. Da die konstante Version immer stärker eingeschränkt ist als die nicht-konstante, sollte auch immer die konstante Version den eigentlichen Code enthalten und durch die nicht-konstante aufgerufen werden.

Da nun beide Funktionen den gleichen Namen haben, muss der Compiler entscheiden, ob die konstante oder die nicht-konstante Version aufgerufen wird. Dies macht er einfach, indem er nachschaut, ob das Objekt konstant ist. Das bedeutet also, Sie können innerhalb einer dieser Funktionen die jeweils andere Version nicht ohne weiteres aufrufen. Die Lösung dieses Dilemmas besteht darin, in der nicht-konstanten Version mittels static_cast Konstantheit zum aktuellen Objekt hinzuzufügen und dann mit dieser konstanten Version des Objektes den Aufruf durchzuführen.

Das zurückgegebene Ergebnis ist nun eine Referenz auf ein konstantes Objekt. Da die nicht konstante Version aber auch eine Referenz auf ein nicht-konstantes Objekt zurückgeben soll, muss nun mittels const_cast die Konstantheit wieder entfernt werden. Beachten Sie hierbei, dass Sie an dieser Stelle sicher wissen, dass dieses konstante Objekt in Wahrheit gar nicht konstant ist. Die Konstantheit, die Sie entfernen, ist die, die Sie zuvor zur Klasse hinzugefügt hatten.

Im folgenden kleinen Beispiel besitzt die Klasse A eine Funktion get(), die Zugriff auf eine Variable wert bietet.

class A{
    int wert;

public:
    int const& get()const; // bietet nur Lesezugriff
    int&       get();      // bietet Schreib-/Lesezugriff 
};

int const& A::get()const{
    return wert;
};

int& A::get(){
    return const_cast< int& >(         // Entfernen der Konstanz für die Rückgabe
        static_cast< A const& >(*this) // Hinzufügen der Konstanz für den Funktionsaufruf
        .get()                         // Aufruf der Konstanten Funktionsvariante
    );
}

Zu beachten ist hierbei noch, dass static_cast eine Referenz auf ein konstantes Objekt erstellen soll. Denn andernfalls wird eine Kopie des aktuellen Objekts erzeugt und dann ist natürlich auch der vom Funktionsaufruf gelieferte Wert Teil der Kopie und nicht des aktuellen Objekts.

Hinweis

Sie sollten unbedingt darauf achten, dass die Variable, auf die Sie eine Referenz zurückgeben, in der Klasse nicht ihrerseits als konstant deklariert ist. Der Compiler wird in diesem Fall keinen Fehler melden! Schreiben Sie bei der Variablendeklaration einen Kommentar, dass Sie aufgrund einer Technik zur Vermeidung von Codeverdopplung (was das größere Übel ist) nicht ohne vorherige Auflösung dieser Technik als Konstante deklariert werden darf, damit auch andere Programmierer wissen, dass dies Probleme mit sich bringt, die nicht unmittelbar erkannt werden.

Dieses Beispiel wird ohne Fehler und Warnung übersetzt, obwohl Schreibzugriff auf eine konstante Variable möglich ist.


class A{
    int const wert;        // Variable ist konstant

public:
    int const& get()const;
    int&       get();      // Erlaubt unerwünschten Schreibzugriff
};

int const& A::get()const{
    return wert;
};

int& A::get(){
    return const_cast< int& >(         // Entfernen der Konstanz für die Rückgabe
        static_cast< A const& >(*this) // Hinzufügen der Konstanz für den Funktionsaufruf
        .get()                         // Aufruf der Konstanten Funktionsvariante
    );
}

dynamic_cast[Bearbeiten]

Der dynamic_cast-Operator ist der einzige Cast-Operator, der zur Laufzeit ausgeführt wird. Zeiger (und auch Referenzen) können neben den zu ihnen gehörenden Datentypen (sofern diese polymorph sind, also mindesten eine virtuelle Methode enthalten) auch auf Objekte zeigen, die von den zu ihnen gehörenden Datentypen abgeleitet wurden. Meistens ist beim Kompilieren noch nicht bekannt, welchen Typ das Objekt, auf das ein Zeiger verweist, genau hat. Mit dynamic_cast kann ein Zeiger sicher in eine abgeleitete Klasse konvertiert werden. Hat das Objekt tatsächlich den Typ, den man für die Umwandlung angegeben hat, dann liefert er wieder die Adresse des Objekt zurück, diesmal natürlich mit dem Datentyp der abgeleiteten Klasse. Hat das Objekt hingegen einen anderen Datentyp als den angegebenen, liefert dynamic_cast einen Nullzeiger zurück. Nach einem Aufruf von dynamic_cast müssen Sie also immer prüfen, ob die Umwandlung erfolgreich war.

#include <iostream>

class A{ public: virtual ~A(){} }; // durch den virtuellen Destruktor polymorph
class B: public A{};       // von A abgeleitet
class C: public A{};       // von A abgeleitet

int main(){
    A* b = new B;
    A* c = new C;

    if(dynamic_cast< B* >(b) != 0)
        std::cout << "b ist vom Typ B" << std::endl;
    else
        std::cout << "b ist nicht vom Typ B" << std::endl;

    if(dynamic_cast< B* >(c) != 0)
        std::cout << "c ist vom Typ B" << std::endl;
    else
        std::cout << "c ist nicht vom Typ B" << std::endl;
}
Ausgabe:
b ist vom Typ B
c ist nicht vom Typ B

reinterpret_cast[Bearbeiten]

Der reinterpret_cast-Operator wird am seltensten verwendet. Er erwartet als Argument einen ganzzahligen Wert, also einen Zeiger, eine Referenz oder einen der Basisdatentypen für ganze Zahlen. Es wird jedoch keine Konvertierung im eigentlichen Sinne durchgeführt, vielmehr wird der übergebene Wert bitweise neu interpretiert. Eine typische Anwendung dieses Operators ist die Interpretation einer Speicheradresse (Zeiger oder Referenz) als Ganzzahl, welche dann beispielsweise in ein Logfile geschrieben wird. Wenn Sie an Produktivcode arbeiten und nicht genau wissen, was Sie tun, dann sollten Sie von diesem Operator besser die Finger lassen. Umso mehr sollten Sie aber den Umgang mit diesem Operator üben, damit Sie wissen was Sie tun, wenn Sie ihn je benötigen sollten.

#include <iostream>

int main(){
    double wert(5); // Adresse des doubles ausgeben
    std::cout << reinterpret_cast< long >(&wert) << std::endl;
}

Gefährliche C-Casts[Bearbeiten]

Bei C-Casts handelt es sich um die Art der Typumwandlung, die aus Gründen der Abwärtskompatibilität zu C übernommen wurde. Im Gegensatz zu C++ gab es in C nur einen Operator, um Datentypen zu konvertieren. Da C keine Klassen und somit auch keine Methoden kennt, ist die Typumwandlung entsprechend auf die Basisdatentypen beschränkt. In C++ wurde der C-Cast auch auf die C++-Strukturen erweitert. Somit können Sie mit einem C-Cast fast alles machen, was mit einem der vier C++-Castoperatoren möglich ist. Ein C-Cast wird mit der folgenden Syntax durchgeführt:

Syntax:
(«Zieldatentyp»)«Variable»
«Nicht-C++-Code», »optional«

Was sofort ins Auge fällt, ist die extrem kurze Syntax. Für einen C++-Castoperator müssen Sie deutlich mehr schreiben.
Es wird empfohlen, C++-Casts zu verwenden, da sie bei fehlerhafter Verwendung zu Compilerfehlern führen, während bei C-Casts ein Programmierfehler nicht unbedingt einen Compilerfehler verursacht, z.B. beim Ändern der Konstantheit. Außerdem wird Lesern des Quellcodes durch C++-Casts grob mitgeteilt, wozu der Cast verwendet wird.

int b;
float c;
const int a = 6;

// Konstantheit entfernen
b = (int) a;

// Zu float konvertieren
c = (float) a;

// Bits als float interpretieren
c = *(float *)&a;

Mit einem C-Cast können Sie über einen Befehl eine Datenumwandlung vornehmen und eine eventuelle Konstantheit entfernen. Mit den C++-Castoperatoren benötigen Sie hierfür zwei Umwandlungen, einerseits einen static_cast für die Typumwandlung und andererseits einen const_cast, um die Konstantheit zu entfernen.
Wenn Sie bei einem static_cast versehentlich eine Typumwandlung für eine konstante Variable in einen nicht-konstanten Datentyp angeben, wird sich der Compiler darüber beschweren. Bei einem C-Cast entsteht kein Fehler, da er nicht auf solche Typumwandlungen beschränkt ist.
Genauso gibt es keinen Fehler, wenn die Dereferenzierung eines Zeigers vor dem Cast vergessen wird.

Was der C-Cast natürlich nicht kann, ist die Funktionalität von dynamic_cast ersetzen. In C gibt es, wie schon erwähnt, keine Klassen und somit auch keine Vererbung und keine Polymorphie, was für dynamic_cast die Voraussetzung ist, um überhaupt sinnvoll zu sein. Wenn Sie versuchen, eine solche Umwandlung vorzunehmen, wird sich der C-Castoperator wie der reinterpret_cast verhalten, was in diesem Zusammenhang nicht beabsichtigt ist.