Manchmal
kann es sinnvoll sein, den Schutz, den das strenge Typsystem der Sprache mit
sich bringt, außer Kraft zu setzen. Schreibt man z.B. (für einen beliebigen Typ
T)
T* pt = new T;
...
void* pv = pt;
pt = pv; //
Fehler!
ist die Zuweisung an pt
aus Sicherheitsgründen nicht erlaubt. Weiß man allerdings wie in unserem
Beispiel hier, dass pv
tatsächlich auf ein T-Objekt
zeigt, kann die Wandlung gefahrlos durchgeführt werden: allerdings muss der
Programmierer dies explizit notieren – denn nur der Programmierer kann wissen,
ob eine solche Wandlung in einer bestimmten Situation gefahrlos möglich ist.
C++ stellt für solche und ähnliche Fälle eine Reihe von Typwandlungsoperatoren
bereit, die jeweils spezielle Aufgaben haben. Darüber hinaus gibt es noch eine
aus Kompatibilitätsgründen aus C übernommene Wandlungsmöglichkeit, die man
allerdings in C++ nicht mehr verwenden sollte.
Folgende Tabelle zeigt eine Übersicht über die C++-Wandlungsoperatoren:
Operator
|
Bedeutung
|
|
|
static_cast
|
„Gutmütige“ (also prinzipiell gefahrlos mögliche) Wandlungen
|
dynamic_cast
|
Wandlungen zwischen Typen in Klassenhierarchien
|
const_cast
|
Entfernen oder Hinzufügen von const
und/oder volatile
|
reinterpret_cast
|
Wandelt zwischen nahezu beliebigen Typen unter Beibehaltung des
Bitmusters
|
|
|
Hinzu kommen die aus C übernommene Wandlungsmöglichkeiten, für die
es zwei Notationen gibt:
Operator
|
Bedeutung
|
|
|
(T)x
|
Wandelt Argument x in Typ T (cast-Notation)
|
T(x)
|
dito (Funktionsnotation)
|
|
|
Alle C++-Wandlungsoperatoren verwenden
die Syntax
operator< T >( arg );
Dabei wird der Wert arg
in den Typ T
gewandelt, sofern der jeweilige Operator und die Situation dies zulassen. Die
Wandlungen werden (mit Ausnahme des Operators dynamic_cast) statisch
durchgeführt, d.h. eine unzulässige Wandlung wird bereits beim Übersetzen
erkannt und führt dann zu einem Syntaxfehler. Operator dynamic_cast kann darüber
hinaus eine dynamische Wandlung durchführen, d.h. hier kann zusätzlich ein
Fehler zur Laufzeit auftreten.
Folgendes Beispiel zeigt die prinzipielle Anwendung der Operatoren
am Beispiel von static_cast:
double d = 2.999;
int i = static_cast<double>(
d );
Die Wandlung gehört zu den Standardkonvertierungen und könnte deshalb
auch implizit ablaufen.
Die Konstruktion
int i = const_cast<double>( d ); // Fehler
liefert hingegen einen Syntaxfehler, da Operator const_cast nur zum Entfernen
(bzw. Hinzufügen) von const
oder volatile
verwendet werden kann.
In den folgenden Abschnitten betrachten wir die einzelnen Wandlungsmöglichkeiten
genauer.
& Operator static_cast<>
Der
Operator ist für alle diejenigen Konvertierungsaufgaben vorgesehen, die „gutmütig“
sind. Dazu gehören:
qStandardkonvertierungen (s.o.)
q Konvertierungen vom Typ T* (für einen beliebigen Typ
T) nach
void* und
zurück. Dabei ist sichergestellt, dass der zurückgewandelte Zeiger den
gleichen Wert hat. Nach der Ausführung der Anweisungen
A* pa = new A;
void* pv = pa; // implizite Wandlung
A* pa2 = static_cast<A*>( pv ); // explizite Wandlung notwendig
haben a und a2 den gleichen Wert, zeigen
also auf das gleiche Objekt. Wird die Wandlung in einen anderen Zeigertyp
durchgeführt, ist das Ergebnis undefiniert:
B* pb = static_cast<B*>( pv ); // undefiniertes Verhalten
q Konvertierung von einem integralen Typ zu
einem Aufzählungstyp. Beispiel:
enum Colors { red, yellow, blue };
int i = 1;
Colors c = static_cast<Colors>(
i );
c hat den Wert yellow
erhalten. Gehört der Quellwert nicht zu den Konstanten
der Aufzählung, ist das Verhalten undefiniert:
Colors c = static_cast<Colors>( 99 ); // undefiniertes Verhalten
q Konvertierungen in Klassenhierarchien, wenn diese
statisch (also ohne Kenntnis des dynamischen Typs eines Zeigers) durchgeführt
werden können.
Der wichtige Punkt bei den Konvertierungen mit static_cast ist
also, dass prinzipiell nicht alle Werte des Quelltyps auch gültige Werte des
Zieltyps sein müssen – aus diesem Grunde laufen die Konvertierungen ja auch
nicht implizit ab. Der Programmierer muss also wissen, was er tut. Tut er das
Richtige, sind Wandlungen mit static_cast
wohldefiniert, ansonsten undefiniert. Welche der beiden Möglichkeiten
vorliegt, hängt vom konkreten Wert ab, der gewandelt werden soll.
Es stellt sich die Frage, warum Standardkonvertierungen in obiger
Liste auftauchen. Standardkonvertierungen können ja implizit ablaufen und
brauchen keine explizite Wandlung. Anstelle von
void f( int i_in )
{
double d = static_cast<double>(
i_in ); // explizite Wandlung
/* ... */
}
kann man direkt
void f( int i_in )
{
double d
= i_in; //
implizite Wandlung
/* ... */
}
schreiben. Beachten Sie jedoch folgenden Fall:
void g( int
); // #1
void g( double
); // #2
void f( int i_in )
{
g( static_cast<double>(
i_in )); // ruft #2
}
Hier möchte der Programmierer die Version #2 der Funktion g für double
aufrufen, der Aktualparameter ist jedoch vom Typ int. Da auch eine Variante
der Funktion g
für int
existiert, ist eine explizite Typwandlung erforderlich.
Beachten Sie bitte, dass das Entfernen von const (const-cast-away) nicht als „gutmütige“ Konvertierung gilt. Die
Konvertierung in
void f( const A* pa )
{
A* pa2 = static_cast<A*>(
pa ); // Fehler!
/* ... */
}
resultiert in einem Syntaxfehler. Zum Entfernen von const ist ausschließlich
Operator const_cast vorgesehen!
Gleiches gilt für die Konvertierung beliebiger Zeigertypen. Es ist
ausdrücklich nicht erlaubt, etwas
wie
struct A {};
struct B {};
A* pa = new A;
B* pb = static_cast<B*>( pa ); //
Fehler!
zu schreiben! Eine solche Konstruktion ist nahezu immer ein Fehler. Es gibt keinen Grund,
einen Zeiger vom Typ A*
auf ein Objekt eines ganz anderen Typs B zeigen zu lassen.
Erlaubt ist allerdings die Wandlung von A* nach void* und von dort aus (mit Hilfe von static_cast)
nach B*.
Dies ist natürlich genauso ein Fehler wie direkt von A* nach B*. Die Konstruktion ist
aber nicht zu verhindern, wenn man die manchmal sinnvollen Wandlungen von A* nach void* und wieder zurück nach A* nicht gänzlich verbieten
will.
Ganz allgemein lässt sich sagen, dass eine Konvertierung mit static_cast
zwischen zwei Typen A
und B
genau dann möglich ist, wenn
qdie Wandlung eine Standardkonvertierung
ist (s.o.)
qdie umgekehrte Wandlung
eine Standardkonvertierung ist.
Ist also die
Anweisungsfolge
A a;
B b;
b = a; // Annahme:
Standardkonvertierung
möglich, ist auch
a = static_cast<A>( b ); //
OK
korrekt. Die Typen A und B sollen hierbei fundamentale
Typen sein, d.h. nicht etwa Klassen mit Konstruktoren oder Wandlungsoperatoren
– diese Fälle besprechen wir später in diesem Kapitel.
Betrachten wir hierzu noch einmal die Wandlungsmöglichkeiten zwischen
Aufzählungen und integralen Typen. Die Wandlung von einem Aufzählungstyp zu
einem int
ist eine Standardkonvertierung und läuft deshalb implizit ab:
enum Colors { red,
yellow, blue };
Color c = yellow;
int i = c; //
OK – implizite Wandlung
Daher lässt sich die umgekehrte Richtung mit Hilfe von static_cast
durchführen:
c = static_cast<Colors>( i ); // OK – explizite Wandlung
Wie immer bei expliziten Typwandlungen ist es die Aufgabe des Programmierers,
vernünftiges Verhalten sicherzustellen. Das folgende Programmsegment ist daher
syntaktisch korrekt, liefert aber undefiniertes Verhalten:
int i = 299;
c = static_cast<Colors>( i ); // OK – aber undefiniertes Verhalten
Schreiben Sie Code, der durch eine Abfrage des Wertes von i prüft, ob
die Wandlung in den Typ Colors
zulässig ist.
Operator
dynamic_cast wird
ausschließlich in Klassenhierarchien verwendet. Polymorphismus erfordert
manchmal bestimmte Konvertierungen, die aber auch gefährlich sein können.
Operator dynamic_cast
erlaubt eine Entscheidung zur
Laufzeit, ob eine Konvertierung möglich ist oder nicht.
Da der Operator ausschließlich in polymorphen Programmen im Zusammenhang
mit der Typidentifizierung zur Laufzeit (run
time type identification, RTTI) verwendet wird, verschieben wir die
Diskussion dieses Operators bis zum Kapitel 34 (Typinformationen zur Laufzeit).
Operator
const_cast kann
ausschließlich dazu verwendet werden, die const- oder volatile-Eigenschaft zu
einem Typ hinzuzufügen oder wegzunehmen. Beispiel:
void f( const A* pa )
{
A* pa2 = const_cast<A*>(
pa ); // OK
/* ... */
}
Hier wird ein Objekt, das eigentlich innerhalb von f nicht verändert werden
soll, über a2 doch
veränderbar. Es ist klar, dass dieses „Wegcasten“ von const(const-cast-away) auf Einzelfälle
beschränkt bleiben soll. In modernem C++ kann man durch Deklaration von mutable oft
auf den const-cast-away verzichten.
Beachten Sie bitte, dass
qdas Entfernen von const nur dann korrekt ist, wenn das eigentliche
Objekt selber nicht konstant ist (kein first
level const), etwa wie in diesem Beispiel:
A a;
f( &a ); //
OK
Das Verhalten dieses
Programmsegments ist dagegen undefiniert:
const A a; //
a ist konstant
f( &a ); //
syntaktisch OK, aber undefiniertes Verhalten
Hier ist das Objekt a tatsächlich
konstant und kann deshalb nicht als nicht-konstantes Objekt behandelt werden.
qder Operator prinzipiell auch zum Zufügen von const verwendet werden kann:
void f( const A* pa )
{
A*
pa2 = const_cast<A*>( pa
);
const
A* pa3 = const_cast<const A*>(
pa2 ); // Zufügen von const
/* ... */
}
Analoges gilt für das Hinzufügen bzw. Wegnehmen von volatile.
Operator
reinterpret_cast
führt keine Wandlung im eigentlichen Sinne aus, sondern interpretiert den durch
eine Variable gegebenen Speicherbereich mit einem anderen Typ. Für die Praxis
wichtig ist, dass dabei das Bitmuster des Speicherbereiches unverändert bleibt.
Typische Anwendungsfälle sind „harte“ Konvertierungen wie in
struct A { /*...*/
};
struct B { /*...*/
};
A* pa;
B* pb = reinterpret_cast<B*>( pa ); // syntaktisch OK. Bedeutung?
Das Ergebnis ist aus Sicht der Sprache undefiniert, es ist allein
Sache des Programmierers, das Layout der Klassen A und B so zu gestalten, dass die
Konvertierung ihren (zweifelhaften) Sinn macht.
Eine häufige Anwendung des Operators ist die Wandlung von Zeigertypen
in numerische Typen und umgekehrt. Unter der Voraussetzung, dass Zeigertypen
die gleiche Größe wie z.B. ints
haben, kann man für einen beliebigen Typ T die Zeilen
T* pt = new T;
int val
= reinterpret_cast<int>( pt );
...
T* pt2 = reinterpret_cast<T*>( val );
schreiben. Dadurch, dass reinterpret_cast das Bitmuster unverändert
lässt, ist sichergestellt, dass pt2
nach der Rückwandlung den gleichen Wert wie pt besitzt.
Beachten Sie bitte, dass über den Wert von val keine Annahmen getroffen
werden können – ausschließlich die Rückwandlung in den Originaltyp ergibt
definiertes Verhalten. Insbesondere ist nicht sichergestellt, dass der
Nullzeiger das gleiche Bitmuster wie die Zahl 0 besitzt. Schreibt man
also
pt = 0;
int
val = reinterpret_cast<int>( pt );
if ( val == 0 ) ... // OK – aber ergibt nicht
unbedingt true
ist nicht unbedingt sichergestellt, dass val den Wert 0 hat.
Schreiben Sie Code, der überprüft, ob Zeigertypen die gleiche Größe
wie ints
haben. Schreiben Sie eine Funktion, die einen beliebigen Zeigertyp in ein int wandelt,
vorher jedoch die Prüfung durchführt. Ist die umgekehrte Funktion (d.h. die
Wandlung eines int
in einen beliebigen Zeigertyp) ebenfalls möglich?
Die Wirkung von reinterpret_cast
ist immer implementierungsabhängig und damit nicht unbedingt
portabel. Die Anwendung sollte auf Ausnahmefälle beschränkt bleiben.
Beachten Sie bitte, dass auch reinterpret_cast nicht zum Entfernen von const verwendet werden kann.
Die folgenden Anweisungen führen zu einem Syntaxfehler:
void f( const A* pa )
{
A* pa2 = reinterpret_cast<A*>(
pa ); // Fehler!
/* ... */
}
Zusätzlich
zu den neuen C++-Wandlungsoperatoren gibt es die aus C geerbten Wandlungsmöglichkeiten.
Für den C-Wandlungsoperator gibt es
zwei Notationen:
qdie
Funktionsnotation T(x)
qdie
cast-Notation (T)x
Beide Notationen sind funktional identisch: sie wandeln den Wert x in einen
Wert vom Typ T,
jedoch gibt es Unterschiede im Aufruf, die durch die Syntax bestimmt sind.
Die Wandlung in Funktionsnotation ist nur möglich, wenn T ein Typ
ist, der sich in einem Wort schreiben lässt. Konstruktionen wie z.B. für eine
Klasse A:
void f( void* p_in)
{
A* ap = A*(p_in);
// Fehler!
}
sind nicht erlaubt. Hier muss die cast-Notation verwendet werden:
void f( void* p_in
)
{
A* ap = (A*)p_in; // OK
}
Der Typ selber kann durchaus ein zusammen gesetzter Typ sein – dann
muss allerdings ein typedef
verwendet werden. Folgendes ist zulässig:
tyedef
A* AP;
void f( void* p_in
)
{
A* ap = (AP)p_in; // OK
}
Demgegenüber kann die Funktionsnotation auch mehr als einen Parameter
wandeln. Notationen wie
A a;
a = A( 1, 2 );
sind möglich. Jedoch benötigt die Klasse A einen Konstruktor, der mit
zwei ints
aufgerufen werden kann. Hierbei handelt es sich um eine benutzerdefinierte
Wandlung mit Klassen, die wir später in diesem Kapitel besprechen werden.
Der C-Wandlungsoperator führt je nach Kontext die Funktionalität
von static_cast,
dynamic_cast,
const_cast oder
reinterpret_cast durch.
Welche Wandlung führt der C-Wandlungsoperator in obigen Beispielen
durch? Schreiben Sie die Beispiele mit dem passenden C++-Wandlungsoperator.
Welche Wandlung
führt der C-Wandlungsoperator in diesem Beispiel durch? Schreiben Sie das
Beispiel mit dem passenden C++-Wandlungsoperator.
enum Colors { red,
yellow, blue };
f( int i_in )
{
Colors c = Colors( i_in ); // Wandlung in Funktionsnotation
...
}