C++-Programmierung/ Nützliches/ Casts

Aus Wikibooks
Zur Navigation springen Zur Suche springen


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.

Crystal Project Tutorials.png
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.

Nuvola-inspired-terminal.svg
1#include <iostream>
2
3int main(){
4    int a = 7, b = 4;
5    double c = static_cast< double >(a) / b;
6}

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.

Nuvola-inspired-terminal.svg
 1class A{
 2    int wert;
 3
 4public:
 5    int const& get()const; // bietet nur Lesezugriff
 6    int&       get();      // bietet Schreib-/Lesezugriff 
 7};
 8
 9int const& A::get()const{
10    return wert;
11};
12
13int& A::get(){
14    return const_cast< int& >(         // Entfernen der Konstanz für die Rückgabe
15        static_cast< A const& >(*this) // Hinzufügen der Konstanz für den Funktionsaufruf
16        .get()                         // Aufruf der Konstanten Funktionsvariante
17    );
18}

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.

Symbol opinion vote.svg
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.

Crystal Clear action button cancel.svg

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


 1class A{
 2    int const wert;        // Variable ist konstant
 3
 4public:
 5    int const& get()const;
 6    int&       get();      // Erlaubt unerwünschten Schreibzugriff
 7};
 8
 9int const& A::get()const{
10    return wert;
11};
12
13int& A::get(){
14    return const_cast< int& >(         // Entfernen der Konstanz für die Rückgabe
15        static_cast< A const& >(*this) // Hinzufügen der Konstanz für den Funktionsaufruf
16        .get()                         // Aufruf der Konstanten Funktionsvariante
17    );
18}

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.

Nuvola-inspired-terminal.svg
 1#include <iostream>
 2
 3class A{ public: virtual ~A(){} }; // durch den virtuellen Destruktor polymorph
 4class B: public A{};       // von A abgeleitet
 5class C: public A{};       // von A abgeleitet
 6
 7int main(){
 8    A* b = new B;
 9    A* c = new C;
10
11    if(dynamic_cast< B* >(b) != 0)
12        std::cout << "b ist vom Typ B" << std::endl;
13    else
14        std::cout << "b ist nicht vom Typ B" << std::endl;
15
16    if(dynamic_cast< B* >(c) != 0)
17        std::cout << "c ist vom Typ B" << std::endl;
18    else
19        std::cout << "c ist nicht vom Typ B" << std::endl;
20}
Crystal Clear app kscreensaver.svg
Ausgabe:
1b ist vom Typ B
2c 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.

Nuvola-inspired-terminal.svg
1#include <iostream>
2
3int main(){
4    double wert(5); // Adresse des doubles ausgeben
5    std::cout << reinterpret_cast< long >(&wert) << std::endl;
6}

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:

Crystal Project Tutorials.png
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.

Nuvola-inspired-terminal.svg
 1int b;
 2float c;
 3const int a = 6;
 4
 5// Konstantheit entfernen
 6b = (int) a;
 7
 8// Zu float konvertieren
 9c = (float) a;
10
11// Bits als float interpretieren
12c = *(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.