© D. Mathieu
mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique
Créé le 20/12/1999 - Dernière mise à jour : 07/03/2001
Operateur dynamic_castBibliographie
Exception bad_cast
Opérateur static_cast
Opérateur const_cast
Opérateur reinterpret_cast
figure 1 : hiérarchie de classe
figure 2 : classe C avec un sous-objet B et un sous-objet A
L'information "run-time" de type permet de savoir si un pointeur pointe actuellement sur un objet complet et peut être transtypé sans risque vers un autre objet dans sa hiérarchie. L'opérateur dynamic_cast peut être utilisé pour effectuer de tels transtypages. En outre il effectue les tests "run-time" nécessaires pour rendre cette opération sûre.
dynamic_cast | utilisé pour la conversion de types polymorphiques |
static_cast | utilisé pour la conversion de types non polymorphiques |
const_cast | utilisé pour supprimer les attributs const, volatile, et __unaligned |
reinterpret__cast | utilisé pour la simple réinterpretation des bits |
N'utiliser const_cast et reinterpret_cast qu'en dernier ressort puisqu'ils présentent les mêmes dangers que les transtypages "old style". Ils sont cependant nécessaires pour remplacer les transtypages dans l'ancien style C.
Syntaxe
dynamic_cast <type-id> (expression) |
Si type-id est un pointeur sur une classe
de base accessible directement ou indirectement sans ambiguïté,
le résultat est un pointeur sur l'unique sous-objet de type type-id.
Par exemple :
class B { ... };
class C : public B { ... }; class D : public C { ... }; void f (D* pd)
|
Ce type de conversion est appelé un “upcast” parce qu'il déplace le pointeur vers le haut de la hiérarchie des classes, à partir d'une classe dérivée vers une classe dont elle dérive. Un upcast est une conversion implicite.
Si type-id est void*, un
test run-time est effectué pour déterminer le type
actuel de l'expression. Le résultat est un pointeur sur l'objet
complet pointé par l'expression. Par exemple :
class A { ... };
class B { ... }; void f() { A* pa = new A; B* pb = new B; void* pv = dynamic_cast <void*> (pa); // pv pointe maintenant sur un objet de type A ... pv = dynamic_cast <void*> (pb); // pv pointe maintenant sur un objet de type B } |
Si type-id n'est pas void*, un test run-time est effectué pour voir si l'objet pointé par l'expression peut être converti dans le type pointé par type-id.
Si le type de expression est une classe
de base du type de type-id, un test run-time est effectué
pour voir si expression pointe actuellement sur un objet complet
du type de type-id. Si c'est vrai, le résultat est un pointeur
vers un objet complet du type de type-id. Par exemple :
class B { ... };
class D : public B { ... }; void f()
D* pd = dynamic_cast <D*>(pb);
// ok: pb pointe actuellement sur un D
|
Ce type de conversion est appelé un “downcast” parce qu'il déplace le pointeur vers le bas de la hiérarchie des classes, à partir d'une classe vers une classe qui en est dérivée. L'héritage multiple peut introduire des ambiguïtés. Considérons la hiérarchie des classes montrée dans la figure 3 :
Un pointeur sur un objet de la classe D
peut être transtypé vers C ou B. Cependant,
s'il est transtypé pour pointer vers un objet de type A,
vers quelle instance? Pour éviter une erreur de transtypage due
à cette ambiguïté, il est possible d'effectuer deux
transtypages non ambigus. Par exemple :
void f()
{ D* pd = new D; A* pa = dynamic_cast<A*>(pd); // erreur: ambigu B* pb = dynamic_cast<B*>(pd); // premier transtypage en B A* pa2 = dynamic_cast<A*>(pb); // ok: non ambigu } |
D'autres ambiguïtés peuvent apparaître si on utilise des classes de base virtuelles. Considérons la hiérarchie de classes illustrée dans la figure 4 :
Dans cette hiérarchie, A est une classe de base virtuelle. Soit une instance de E et un pointeur vers la classe de base virtuelle A. Un dynamic_cast sur un pointeur vers B échouera à cause d'une ambiguïté. Il faut tout d'abord transtyper sur l'objet complet E, puis remonter dans la hiérarchie de manière non ambiguë jusqu'à atteindre l'objet B correct.
Considérons maintenant la hiérarchie montrée dans la figure 5 :
Soit un objet de type E et un pointeur sur
le sous-objet D, trois conversions sont nécessaires pour
aller du sous-objet D au sous-objet A le plus à
gauche : il faut tout d'abord effectuer une conversion dynamic_cast
du pointeur D vers un pointeur E, puis une conversion
(soit dynamic_cast soit implicite) de E vers B,
et enfin une conversion implicite de B vers A. Par exemple:
void f(D* pd)
{ E* pe = dynamic_cast <E*>(pd); B* pb = pe; // upcast, conversion implicite A* pa = pb; // upcast, conversion implicite } |
L'opérateur dynamic_cast peut aussi
être utilisé pour effectuer un transtypage croisé ("cross
cast"). En utilisant la même hiérarchie (figure 5), il
est possible de transtyper un pointeur, par exemple du sous-objet B
vers le sous-objet D, sous réserve que l'objet complet
soit de type E. Il est possible de n'effectuer la conversion
d'un pointeur sur D en un pointeur sur le sous-objet A
le plus à gauche qu'en deux opérations : un transtypage croisé
de D vers B, puis une conversion implicite de B
vers A. Par exemple :
void f(D* pd)
{ B* pb = dynamic_cast <B*>(pd); // transtypage croisé A* pa = pb; // upcast, conversion implicite } |
Une valeur de pointeur nulle est convertie en une
valeur de pointeur nulle du type de destination par dynamic_cast.
Si expression ne peut être proprement convertie dans
le type type-id lors de l'utilisation de dynamic_cast<type-id>(expression),
le test run-time conduit à un échec. Par exemple :
class A { ... };
class B { ... }; void f() { A* pa = new A; B* pb = dynamic_cast <B*>(pa); // échec : B ne dérive pas de A ... } |
La valeur retournée par un transtypage dynamique de pointeur est un pointeur nul si l'expression est un pointeur. L'échec d'un transtypage en une référence lève l'exception bad_cast.
class bad_cast : public logic {
public: bad_cast (const __exString& what_arg) : logic (what_arg) {} void raise() { handle_raise(); throw *this; } // virtual __exString what() const; // héritée }; |
Syntaxe
static_cast <type-id> (expression) |
L'opérateur static_cast peut être
utilisé pour des opérations telles que la conversion d'un
pointeur d'une classe de base en un pointeur vers une classe dérivée.
De telles conversions ne sont pas toujours sûres. Par exemple :
class B { ... };
class D : public B { ... }; void f(B* pb, D* pd) { D* pd2 = static_cast <D*>(pb); // pas sûre, pb peut ne pointer que sur un B B* pb2 = static_cast <B*>(pd); // conversion sûre ... } |
Contrairement à dynamic_cast, aucun
test run-time n'est effectué lors de la conversion static_cast
de pb. L'objet pointé par pb peut ne pas être
de type D, auquel cas l'utilisation de *pd2 pourrait
être désastreuse. Par exemple l'appel d'une fonction membre
de la classe D, mais pas de la classe B, pourrait provoquer
une violation d'accès.
class B { ... };
class D : public B { ... }; void f(B* pb)
|
Si pb pointe réellement sur un objet de type D, alors pd1 et pd2 auront la même valeur, de même que si pb == 0.
Si pb pointe sur un objet de type B et pas sur la classe complète D, alors dynamic_cast en sait suffisamment pour retourner 0. Cependant, static_cast fait confiance au programmeur, suppose que pb pointe réellement sur un objet de type D, et retourne simplement un pointeur sur ce supposé objet D. En conséquence, static_cast peut faire l'inverse des conversions implicites, et les résultats peuvent être indéfinis. C'est de la responsabilité du programmeur de s'assurer que la conversion static_cast est applicable.
Ce comportement s'applique aussi aux types autres que les types de classes. Par exemple, static_cast peut être utilisé pour convertir un int en un char. Cependant, le char peut ne pas avoir suffisamment de bits pour contenir la valeur int. C'est de nouveau au programmeur de s'assurer de la validité de l'opération.
L'opérateur static_cast peut aussi être utilisé pour effectuer toute conversion implicite, standard ou définie par l'utilisateur.
Par exemple :
typedef unsigned char BYTE
void f() { char ch; int i = 65; float f = 2.5; double dbl; ch = static_cast <char>(i);
// int en char
|
L'opérateur static_cast peut explicitement convertir une valeur entière en un type énuméré. Si la valeur entière ne tombe pas dans le domaine des valeurs énumérées, la valeur résultante d'énumération est indéfinie.
L'opérateur static_cast convertit
une valeur de pointeur nulle en une valeur de pointeur nulle du type de
destination.
Toute expression peut être explicitement convertie en un type
void par l'opérateur static_cast. Le type de destination
void peut inclure de façon optionnelle les attributs const,
volatile, et __unaligned.
Syntaxe
const_cast <type-id> (expression) |
Un pointeur sur n'importe quel objet ou sur une donnée membre peut être explicitement converti en un type identique à part les qualifieurs const, volatile, et __unaligned. Pour les pointeurs et les références, le résultat référera l'objet original. Pour les pointeurs sur données membres, le résultat référera la même donnée membre que le pointeur original. Selon le type d'objet référencé, une opération d'écriture par le biais du pointeur résultant, d'une référence ou d'un pointeur sur donnée membre peut produire un comportement indéfini.
L'opérateur const_cast convertit un pointeur de valeur nulle en un pointeur de valeur nulle du type destination.
Syntaxe
reinterpret_cast <type-id> (expression) |
L'opérateur reinterpret_cast peut être utilisé pour des conversions telles que char* en int*, ou One_class* en Unrelated_class*, qui sont par nature peu sûrs.
Le résultat de reinterpret_cast ne peut être utilisé de façon sûre pour autre chose que le retour au type d'origine. Toute autre utilisation est, au mieux, non portable.
L'opérateur reinterpret_cast convertit un pointeur de valeur nulle en un pointeur de valeur nulle du type destination.
© D. Mathieu
mathieu@romarin.univ-aix.fr
I.U.T.d'Aix en Provence - Département Informatique