Zusätzlich zu der ,,alten`` Möglichkeit in C, eine Typkonversion (cast) durchzuführen, indem man den gewünschten Typ in Klammern vor einen Ausdruck schreibt, kommen jetzt weitere Varianten hinzu, die wesentlich sicherer zu verwenden sind.
Ein Problem mit der alten cast-Version besteht darin, daß häufig unklar ist, was wirklich passiert.
Beispiele:
int i = 3; up( (double)i );
konvertiert eine lokale Kopie
von i (ein
temporäres Objekt) aktiv in den Typ double. Dabei wird
tatsächlich eine sinnvolle Konversion vorgenommen; der an up()
übergebene Wert ist 3.0.
int i=3; *(double*)&i = 3.0
dagegen konvertiert nichts!
Vielmehr wird &i
, also die Adresse von i, als Zeiger auf
eine double betrachtet ((double*)&i
). Dann wird dahin,
worauf diese Adresse zeigt (an die Stelle *(double*)&i
im
Speicher) ein double-Wert
(3.0) geschrieben. Auf üblichen Rechnern ist eine int 4 Byte
groß, eine double dagegen 8 oder mehr Byte. Es wird also über die 4
Byte von i hinaus weiterer dahinterliegender Speicher mit dem
Bitmuster der Gleitkommazahl 3.0 überschrieben; in i sowie
in dem nicht mehr dazugehörigen dahinterliegenden Speicher steht
Schrott.
Der alte cast bewirkt also je nach Umständen manchmal wirklich eine sinnvolle Konversion, manchmal dagegen nur ein Neuinterpretieren eines Bitmusters, im obigen Beispiel einer Adresse. Gerade beim Interpretieren eines Zeigers hat man kaum Kontrolle, ob der cast Sinn macht.
Um die Absicht des Programmierers klarer auszudrücken, sollte der bisherige cast nur noch möglichst wenig verwendet werden. Stattdessen bieten sich die folgenden neuen Operatoren an:
dynamic_cast<T>
dient dazu,
ein sicheres down cast
durchzuführen. Damit ist gemeint, einen Zeiger auf ein Objekt einer
Basisklasse in einen Zeiger auf ein Objekt einer davon abgeleiteten
Klasse zu konvertieren (oder analog mit Referenzen).
Die Richtung im Begriff down cast bezieht sich auf die übliche
Darstellungsweise wie in Abbildung 4.1, bei der die
Basisklasse
oben gezeigt wird, und die davon abgeleiteten Klassen jeweils nach
unten angehängt werden. dynamic_cast<T>
konvertiert also einen
Zeiger (oder eine Referenz), der als Zeiger (oder Referenz) auf eine
Basisklasse deklariert ist, in Richtung der abgeleiteten Klasse T.
Die Konvertierung gelingt, wenn folgende Voraussetzungen erfüllt sind:
Wenn die Konvertierung gelingt, dann wird ein Zeiger vom Typ T*
beziehungsweise eine Referenz vom Typ T&
auf das ursprüngliche
Objekt geliefert; das Objekt selbst wird dabei nicht verändert.
Im Fehlerfall wird bei Aufruf mit einem Zeiger der Wert NULL
geliefert; bei einem Aufruf mit einer Referenz dagegen wird eine
Ausnahme vom Typ bad_cast
geworfen (siehe
Ausnahmen der Standardbibliothek).
In den beteiligten Klassen muß mindestens eine virtuelle Methode vorhanden sein.
Beispiel:
#include <iostream> using namespace std; class CA { public: virtual ~CA(){} }; class CB : public CA { public: virtual ~CB(){} }; class CC : public CB { public: virtual ~CC(){} }; void demo_dynamic_cast_Zeiger() { { // das klappt (down cast): CC cc; CA *ptr = &cc; cout << "ptr = " << (void*)(dynamic_cast<CC*>(ptr)) << endl; } //{ // // das klappt NICHT (up cast): // CA ca; // CC *ptr = &ca; // cout << "ptr = " << (void*)(dynamic_cast<CA*>(ptr)) << endl; //} } void demo_dynamic_cast_Referenz() { { // das klappt (down cast): CC cc; CA &ref = cc; cout << "cc = " << (void*)&(cc) << endl; cout << "ptr = " << (void*)&((dynamic_cast<CC&>(ref))) << endl; } //{ // // das klappt NICHT (up cast): // CA ca; // CC &ptr = ca; // cout << "ca = " << (void*)&(ca) << endl; // cout << "ptr = " << (void*)&((dynamic_cast<CA&>(ref))) << endl; //} } int main( int nargs, char **args ) { demo_dynamic_cast_Zeiger(); demo_dynamic_cast_Referenz(); return 0; } // main( int nargs, char **args )
static_cast<T>
erzwingt
eine Konvertierung im Sinne des
ursprünglichen cast (also (T)wert). Dabei führt der
Compiler eine Konvertierung durch, soweit er kann (beispielsweise double nach int und ähnliches). Dies macht Sinn für die
Konvertierung verschiedener einfacher Datentypen untereinander,
zwischen Zeigern auf Objekte einer Klassenhierarchie, oder zwischen
enum-Werten und integralen (also ganzzahligen) Werten.
Ein static_cast<T>
berücksichtigt const-Deklarationen und
die Zugriffsrechte gemäß public, private und protected.
Wenn die Konvertierung nicht möglich ist, tritt ein Übersetzungsfehler auf.
reinterpret_cast<T>
bewirkt, daß das Bitmuster eines
gegebenen Ausdrucks einfach als ein anderer Typ angesehen wird. Der
Compiler führt tatsächlich keine Konversion durch!
Angewendet werden kann das sinnvoll für Zeiger, wenn man sich über die Folgen im klaren ist. Ansonsten sollte man die Finger von explosiven oder stromführenden Gegenständen fernhalten.
const_cast<T>()
konvertiert
nicht etwa irgendein Objekt in ein äquivalentes const-Objekt, wie
der Name glauben machen könnte. Ganz im Gegenteil wird von einem
const-Objekt das const-Attribut abgestreift.
Der Typ T muß eine Referenz oder ein Zeiger sein; das abgestreifte const bezieht sich auf das referenzierte bzw. verwiesene Objekt.
Beispiel:
int i; const int &iref = i; const int *iptr = &i; i = 10; // zulässig, da i nicht const ist //iref = 10; // nicht zulässig, da iref eine const-Referenz ist //*iptr = 10; // nicht zulässig, da iptr ein const-Zeiger ist const_cast<int&>(iref) = 10; // ok *const_cast<int*>(iptr) = 10; // ok
Es ist nicht möglich, mit einem const_cast
eine Konstante zu
einer (nicht-const-) Variablen zu machen:
const int j = 0; //const_cast<int>(j) = 10; // geht nicht, da j nicht Ref. oder ZeigerMit
const_cast
wird also nur das const einer Referenz
oder eines Zeigers auf ein Objekt abgestreift, welches wiederum
nicht const sein darf.
Im obigen Fall wird der Fehler vom Compiler erkannt.
In dem Beispiel:
const int ci=0; const int &ciref = ci; const int *ciptr = &ci; //i = 10; // zulässig, da i nicht const ist //iref = 10; // nicht zulässig, da iref eine const-Referenz ist //*iptr = 10; // nicht zulässig, da iptr ein const-Zeiger ist const_cast<int&>(ciref) = 10; // ok *const_cast<int*>(ciptr) = 10; // ok std::cout << " ci = " << ci << std::endl; std::cout << " ciref = " << ciref << std::endl; std::cout << " *ciptr = " << *ciptr << std::endl;wird mit dem
const_cast
wiederum nur das const der
Referenz beziehungsweise des Zeigers abgestreift, aber das verwiesene
Objekt selbst ist ebenfalls const, was durch const_cast
nicht geändert wird. Damit ist der modifizierende Zugriff auf ci
über die Referenz und den Zeiger zwar ebenfalls nicht zulässig, aber
der Compiler wird das nicht erkennen können. Dann ist nicht definiert,
was passiert.
Der GNU-Compiler (g++ 3.4.3, Linux 2.6) erzeugt folgende Ausgabe:
ci = 0 ciref = 10 *ciptr = 10Obwohl sowohl ci als auch ciref und
*ciptr
sich auf
dieselbe Variable beziehen sollten, haben sie bei der Ausgabe
unterschiedliche Werte!
Dies liegt daran, daß die const-Deklaration nicht unbedingt ein
nicht veränderbares Objekt erzeugt, sondern nur besagt: ,,Der
Programmierer will das Objekt nicht ändern`` (ein Versprechen an den
Compiler, das er durch den Zugriff über die Referenz und den Zeiger
gleich wieder bricht).
Der Compiler verläßt sich aber darauf, daß ci nicht geändert
wird, und erzeugt die Ausgabe von ci nicht durch Lesen des
const-Objekts aus dem Speicher, sondern gleich durch Einsetzen
des Werts in den Code zum Aufruf von cout <<
...; aus seiner
sollte es ja gleichwertig sein (aber schneller, als den vermeintlich
bekannten Wert aus dem Speicher zu lesen). An der Ausgabe von
ciref und ciptr erkennt man jedoch, daß ci
tatsächlich geändert wurde.
Andere Compiler können das anders handhaben; möglicherweise sind alle drei ausgegebenen Werte gleich, oder das Programm bricht zur Laufzeit ab, wenn ci in einem tatsächlich schreibgeschützten Speicherbereich liegt.
Um die Ausgabe wenigstens konsistent zu machen, muß man ci als volatile deklarieren (um dem Compiler zu sagen, daß sich trotz des const der Wert durch irgendwelche Maßnahmen ändern kann, die sich seiner Kontrolle entziehen):
const volatile int ci=0; const volatile int &ciref = ci; const volatile int *ciptr = &ci; const_cast<int&>(ciref) = 10; // ok *const_cast<int*>(ciptr) = 10; // ok std::cout << " ci = " << ci << std::endl; std::cout << " ciref = " << ciref << std::endl; std::cout << " *ciptr = " << *ciptr << std::endl;Jetzt ist auch bei der Ausgabe von ci der Wert 10 zu sehen, weil sich der Compiler nicht mehr auf den unveränderten Wert verläßt, sondern zur Ausgabe extra auf die Variable zugreift. Siehe dazu auch die Diskussion zu const und volatile in [KW:0 ist false] (Abschnitt Daten, dort Attributangaben).
Weiterhin gehört zur Gruppe der RTTI-Operatoren
noch typeid()
. Damit kann zur Laufzeit
der Typ eines Ausdrucks oder eines Typnamens in Erfahrung gebracht
werden. Ähnlich wie sizeof() kann typeid()
mit einem Typ
oder einem Ausdruck aufgerufen werden. Geliefert wird ein Objekt vom
Typ type_info
(deklariert in <typeinfo>
), das allerdings
nicht gespeichert werden kann (weil der Konstruktor private
deklariert ist).
Man kann aber zwei Objekte davon vergleichen, sowie mit der Methode name() einen String liefern lassen, der eine textuelle Beschreibung des Typs liefert (der Text ist aber systemabhängig).
Wenn typeid() für einen NULL-Zeiger aufgerufen wird, wird
eine Ausnahme vom Typ bad_typeid
geworfen
(Ausnahmen der Standardbibliothek).
Beispiel:
#include <iostream> #include <typeinfo> using namespace std; class A { }; int main( int nargs, char **args ) { A a; cout << ( typeid( A )==typeid( a ) ? "gleich" : "nicht gleich" ) << endl; cout << " Name = " << typeid( A ).name() << endl; return 0; }
AnyWare@Wachtler.de