Advanced Services | cast-Operatoren |
In C und C++ gab es lange Zeit nur einen Cast-Operator, ausführlich auch Typecast genannt. Man schreibt den gewünschten Datentyp in eine Klammer vor der Variablen und ermöglicht dadurch eine Zuweisung zu einer Variablen des gewünschten Typs.
Das Beispiel ist ein reines C Beispiel. Die Funktion malloc() besorgt Speicher und liefert einen untypisierten void-Pointer zurück. In C brauchen wir hierfür ein #include <stdlib.h>, in C++ wird das zu #include <cstdlib>
#include <cstdlib> void beispiel() { void* poi = malloc(400); //int *intpoi = poi; // NOK: invalid conversion from 'void*' to 'int*' int *intpoi = (int*)poi; // OK }
Wenn wir den int-Pointer als Pointer auf ein int-Array auffassen ist es schöner ihn konstant anzulegen. Das kann man erreichen, indem man const entsprechend einsetzt, das Casten zu einem Arraytyp direkt läßt der Compiler jedoch nicht zu.
#include <cstdlib> void beispiel() { void* poi = malloc(400); //int intarr[] = (int[])poi; // NOK: ISO C++ forbids casting to an array type int *const intarr = (int *const)poi; //OK }
Für die folgende Situation braucht man keinen Cast.
void beispiel() { double dou = 42.84; int i = dou; cout << i << endl; // 42 }
Trotz Datenverlust bringt ein C/C++ Compiler je nach Einstellung höchstens ein Warning. man kann zur Verdeutlichung aber auch einen Cast ergänzen.
void beispiel() { double dou = 42.84; int i = (int)dou; cout << i << endl; // 42 }
Der klassische Cast ist ein sehr mächtiger Cast, man kann praktisch alles zu allem casten womit man ziemlichen Schaden anrichten kann. Hier ein drastisches Beispiel.
void dangerous() { int x = 123; char* charpoi = (char*)x; cout << charpoi << endl; // Laufzeitfehler }
Hier wird der int-Wert als Speicheradresse aufgefaßt, auf die ein char-Pointer zeigt. cout versucht dann ab dieser Adresse einen nullterminierten C-String auszulesen...
Um Code sicherer, besser lesbar und wartbar zu machen wurden in C++ 4 neue Castformen eingeführt, die unterschiedliche Anwendungen abdecken.
Die Reihenfolge ist ungefähr von schwach nach stark, const_cast und static_cast gelten als schwache Casts, die letzten beiden als starke Casts. Der stärkste Cast ist aber immer noch der alte von C herkommende Cast.
Der const_cast erlaubt lediglich die Zuweisung zum gleichen Datentyp der aber kein const-Typ ist. Er kann nur für Pointer- und Referenztypen eingesetzt werden. In diesen Fällen ist er aber mächtig, denn nach dem casten ist die Variable nicht mehr konstant. Eine Sonderrolle spielt hier der klassische C-String, der auch nach Anwendung von const_cast nicht veränderbar ist. Die folgenden Beispiele zeigen das Verhalten.
Mit int-Pointer
void const_cast() { int a = 42; const int* poi = &a; //*poi = 43; // assignment of read-only location '* poi' //int *poi2 = poi; // invalid conversion from 'const int*' to 'int*' int *poi2 = const_cast<int*>(poi); *poi2 = 43; cout << *poi2 << endl; // 43 }
Mit char-Pointer
void const_cast() { char ch = 42; const char* cpoi = &ch; char* cpoi2 = const_cast<char*>(cpoi); *cpoi2 = 65; // OK cout << *cpoi2 << endl; // A }
Mit C-String (Sonderrolle der C-Strings)
void const_cast() { const char* cs = "Hallo"; char* cs2 = const_cast<char*>(cs); // *cs2 = 'h'; // Build Ok, aber Laufzeitfehler }
"Hallo" wird in der data-section in einem stringpool abgelegt. Bei einem weiteren Anlegen eines C-Strings "Hallo" wird keine neuer Speicher angeschafft, lediglich ein neuer Pointer.
Mit Klassenpointer
class X { public: int z; };
void const_cast() { X x; const X* xpoi = &x; //xpoi->z = 17; // assignment of member 'X::z' in read-only object X* xpoi2 = const_cast<X*>(xpoi); xpoi2->z = 17; cout << xpoi2->z << endl; }
Mit char-Referenz
void const_cast() { char cha = 42; const char &ref = cha; //ref = 65; // assignment of read-only reference 'ref' char &ref2 = const_cast<char&>(ref); ref2 = 65; // OK cout << ref2 << endl; // A }
Mit Referenz auf C-String
void const_cast() { const char* cs = "Hallo"; const char* &cref = cs; char* &cref2 = const_cast<char* &>(cref); //*cref2 = 'h'; // Build OK, aber Laufzeitfehler }
Mit Klassenreferenz
class X { public: int z; };
void const_cast() { X x; const X &xref = x; //xref.z = 17; // assignment of member 'X::z' in read-only object X &xref2 = const_cast<X&>(xref); xref2.z = 17; cout << xref2.z << endl; }
Notwendig ist dieser Cast bei der Übergabe von const-Variablen an Funktionen die keinen const-Parameter in der Parameterliste haben.
void const_cast_demo() { const char* cstr = "Hallo"; //no_const_param(cstr); // NOK: invalid conversion from 'const char*' to 'char*' no_const_param(const_cast<char*>(cstr)); // OK } void no_const_param(char* cstr) { cout << cstr << endl; }
static_cast erlaubt das Konvertieren von verwandten Datentypen. So kann man ihn etwa einsetzen um eine Konvertierung mit Datenverlust deutlich zu machen. Eine Konvertierung von etwa double nach int liefert ja bei einem C/C++ Compiler höchstens ein Warning. Um solche Situationen deutlicher zu machen, kann man und sollte man einen static_cast verwenden.
Optionale Verwendung von static_cast.
void static_cast() { double dou = 42.84; int i = static_cast<int>(dou); cout << i << endl; // 42 }
Interessanter ist, daß man static_cast auch bei verwandten Pointertypen einsetzen kann. In solchen Fällen ist er notwendig.
Casten eines void-Pointers zu einem typisierten Pointer (Cast notwendig)
void static_cast() { void* voidpoi = malloc(400); int *intpoi = static_cast<int*>(voidpoi); // OK int *const intarr = static_cast<int *const>(voidpoi); //OK }
Der void-Pointer kann mit Hilfe des static_cast zu jedem anderen Pointertyp gecastet werden.
Für die umgekehrte Richtung ist kein Cast notwendig.
void static_cast() { int x = 123; int* intpoi = &x; void* voidpoi = intpoi; // kein Cast notwendig }
Bei unterschiedlich typisierten Pointern ist der static_cast jedoch nicht anwendbar.
void static_cast() { int y = 42; int* intpoi = &y; //char* charpoi = static_cast<char*>(intpoi); // NOK: invalid static_cast from type 'int*' to type 'char*' char ch2 = 'A'; char* charpoi = &ch2; //int* intpoi2 = static_cast<int*>(charpoi); // NOK: invalid static_cast from type 'char*' to type 'int*' }
Schaltet man jedoch einen void-Pointer dazwischen kann man diese Einschränkung umgehen.
In folgendem Codeschnipsel holt man sich Speicher für sieben int-Variablen und nützt diesen Speicher anschließend um char-Variable zu speichern.
void static_cast() { int *intarr= new int[7]; // Heap void * vpoi = intarr; char *chararr = static_cast<char*>(vpoi); // jetzt hat man ein char-Array der Länge sizeof(int)*7 = 28 (falls ein int 4 byte hat) for(int i=0; i<27; i++) chararr[i] = 97 + i ; chararr[27] = 0; // stringende cout << chararr << endl; }
static_cast für verwandte Klassentypen.
class Parent { }; class Child1 : public Parent { }; class Child2 : public Parent { };
void static_cast() { Child1 child1; Parent* ppoi = &child1; Child1 *childpoi = static_cast<Child1*>(ppoi); }
Ein static_cast von Child1 zu Child2 ist dagegen (aus gutem Grund) nicht möglich.
Mit diesem Cast kann man den Datentyp einer Variablen "gewaltsam" ändern. Entsprechend vorsichtig muß man mit diesem Instrument umgehen.
void reinterpret_cast_demo_1() { // Fasse einen char-Pointer als Ganzzahl auf int x = reinterpret_cast<int>(poi); // OK cout << "poi = " << poi << endl; cout << "vpoi = " << vpoi << endl; cout << "x = " << x << endl; }
Das umgekehrte ist nicht zu empfehlen.
void reinterpret_cast_demo_2() { // Fasse ein int als char* auf int y = 43; char* cpoi = reinterpret_cast<char*>(y); // Build OK, aber... cout << "cpoi = " << cpoi << endl; // Laufzeitfehler }
Der dynamic_cast kann nur auf Pointer oder Referenzen angewendet werden. Außerdem muß der zu castende Typ muß polymorph sein, d.h. die zugehörige Klasse hat (mindestens) eine virtuelle Methode. Es reicht z. Bsp. der Destruktor, auch kann die Methode public, protected oder private sein.
Ein dynamic_cast benützt im Hintedrgrund RTTI (Run-Time Type Information) um zur Laufzeit Information zu bekommen. Bei den meisten Compilern ist RTTI als default eingeschaltet. Wenn nicht, so muß man den Compilerschalter per Hand setzen.
Anhand der VMT (virtual methode table) wird geprüft, ob der cast durchführbar ist oder nicht.
Handelt es sich um einen nichtkonvertierbaren Pointer so liefert dynamic_cast einen Nullpointer.
Handelt es sich um eine nichtkonvertierbare Referenz so wird eine Exception vom Typ bad_cast geworfen.
Ein Beispiel mit Referenzen
void dynamic_cast_mit_referenzen() { Base base ; Derived der; Base& bref = base ; Derived& dref = der; bref = dref; // kein cast notwendig, aber möglich bref = dynamic_cast<Base&>(dref); // dynamic_cast<Base&> verlangt runde Klammer try { //Derived& der2 = base; // cannot convert Base to Derived& Derived& der2 = dynamic_cast<Derived&>(base); // kein compilerfehler aber exception bad_cast wird geworfen } catch (bad_cast ex) { cout << ex.what() << endl; // Bad dynamic_cast! } }