Les règles de dérivation permettent d'assurer le fait que lorsqu'on utilise un pointeur sur une classe, l'objet pointé existe bien et est bien de la classe sur laquelle le pointeur est basé. En particulier, il est possible de convertir un pointeur sur un objet en un pointeur sur un sous-objet.
En revanche, il est interdit d'utiliser un pointeur sur une classe de base pour initialiser un pointeur sur une classe dérivée. Pourtant, cette opération peut être légale, si le programmeur sait que le pointeur pointe bien sur un objet de la classe dérivée. Le langage exige cependant un transtypage explicite. Une telle situation demande l'analyse du programme afin de savoir si elle est légale ou non.
Parfois, il est impossible de faire cette analyse. Cela signifie que le programmeur ne peut pas certifier que le pointeur dont il dispose est un pointeur sur un sous-objet. Le mécanisme d'identification dynamique des types peut être alors utilisé pour vérifier, à l'exécution, si le transtypage est légal. S'il ne l'est pas, un traitement particulier doit être effectué, mais s'il l'est, le programme peut se poursuivre normalement.
Le C++ fournit un jeu d'opérateurs de transtypage qui permettent de faire ces vérifications dynamiques, et qui donc sont nettement plus sûrs que le transtypage tout puissant du C que l'on a utilisé jusqu'ici. Ces opérateurs sont capables de faire un transtypage dynamique, un transtypage statique, un transtypage de constance et un transtypage de réinterprétation des données. Nous allons voir les différents opérateurs permettant de faire ces transtypages, ainsi que leur signification.
Le transtypage dynamique permet de convertir
une expression en un pointeur ou une référence d'une classe, ou un pointeur sur void.
Il est réalisé à l'aide de l'opérateur dynamic_cast
. Cet opérateur impose
des restrictions lors des transtypages afin de garantir une plus grande fiabilité :
il effectue une vérification de la validité du transtypage ;
il n'est pas possible d'éliminer les qualifications
de constance (pour cela, il faut utiliser l'opérateur const_cast
, que l'on verra
plus loin).
En revanche, l'opérateur dynamic_cast
permet
parfaitement d'accroître la constance d'un type complexe, comme le font les conversions implicites
du langage vues dans la Section 3.2 et dans la Section 4.7.
Il ne peut pas travailler sur les types de base du langage, sauf void *.
La syntaxe de l'opérateur dynamic_cast
est donnée
ci-dessous :
dynamic_cast<type>(expression)où type désigne le type cible du transtypage, et expression l'expression à transtyper.
Le transtypage d'un pointeur ou d'une référence d'une classe dérivée en classe de base se fait donc directement, sans vérification dynamique, puisque cette opération est toujours valide. Les lignes suivantes :
// La classe B hérite de la classe A : B *pb; A *pA=dynamic_cast<A *>(pB);sont donc strictement équivalentes à celles-ci :
// La classe B hérite de la classe A : B *pb; A *pA=pB;
Tout autre transtypage doit se faire à partir d'un type polymorphique,
afin que le compilateur puisse utiliser l'identification dynamique des types lors du transtypage.
Le transtypage d'un pointeur d'un objet vers un pointeur de type void renvoie l'adresse
du début de l'objet le plus dérivé, c'est-à-dire l'adresse de l'objet complet. Le transtypage
d'un pointeur ou d'une référence sur un sous-objet d'un objet vers un pointeur ou une référence
de l'objet complet est effectué après vérification du type dynamique. Si l'objet pointé ou référencé
est bien du type indiqué pour le transtypage, l'opération se déroule correctement. En revanche, s'il
n'est pas du bon type, dynamic_cast
n'effectue pas le transtypage. Si le type
cible est un pointeur, le pointeur nul est renvoyé. Si en revanche l'expression caractérise un objet
ou une référence d'objet, une exception de type bad_cast est lancée.
La classe bad_cast est définie comme suit dans l'en-tête typeinfo :
class bad_cast : public exception { public: bad_cast(void) throw(); bad_cast(const bad_cast&) throw(); bad_cast &operator=(const bad_cast&) throw(); virtual ~bad_cast(void) throw(); virtual const char* what(void) const throw(); };
Lors d'un transtypage, aucune ambiguïté ne doit avoir lieu pendant
la recherche dynamique du type. De telles ambiguïtés peuvent apparaître dans les cas d'héritage multiple,
où plusieurs objets de même type peuvent coexister dans le même objet. Cette restriction mise à part,
l'opérateur dynamic_cast
est capable de parcourir une hiérarchie de classe aussi
bien verticalement (convertir un pointeur de sous-objet vers un pointeur d'objet complet) que
transversalement (convertir un pointeur d'objet vers un pointeur d'un autre objet frère dans
la hiérarchie de classes).
L'opérateur dynamic_cast
peut être utilisé dans
le but de convertir un pointeur sur une classe de base virtuelle vers une des ses classes filles,
ce que ne pouvaient pas faire les transtypages classiques du C. En revanche, il ne peut pas être utilisé
afin d'accéder à des classes de base qui ne sont pas visibles (en particulier, les classes de base
héritées en private).
Exemple 10-2. Opérateur dynamic_cast
struct A { virtual void f(void) { return ; } }; struct B : virtual public A { }; struct C : virtual public A, public B { }; struct D { virtual void g(void) { return ; } }; struct E : public B, public C, public D { }; int main(void) { E e; // e contient deux sous-objets de classe B // (mais un seul sous-objet de classe A). // Les sous-objets de classe C et D sont // frères. A *pA=&e; // Dérivation légale : le sous-objet // de classe A est unique. // C *pC=(C *) pA;// Illégal : A est une classe de base // virtuelle (erreur de compilation). C *pC=dynamic_cast<C *>(pA); // Légal. Transtypage // dynamique vertical. D *pD=dynamic_cast<D *>(pC); // Légal. Transtypage // dynamique horizontal. B *pB=dynamic_cast<B *>(pA); // Légal, mais échouera // à l'exécution (ambiguïté). return 0 ; }
Contrairement au transtypage dynamique, le transtypage statique n'effectue aucune vérification des types dynamiques lors du transtypage. Il est donc nettement plus dangereux que le transtypage dynamique. Cependant, contrairement au transtypage C classique, il ne permet toujours pas de supprimer les qualifications de constance.
Le transtypage statique s'effectue à l'aide de l'opérateur
static_cast
, dont la syntaxe est exactement la même que celle de l'opérateur
dynamic_cast
:
static_cast<type>(expression)où type et expression ont la même signification que pour l'opérateur
dynamic_cast
.
Essentiellement, l'opérateur static_cast
n'effectue
l'opération de transtypage que si l'expression suivante est valide :
type temporaire(expression);
Cette expression construit un objet temporaire quelconque de type type
et l'initialise avec la valeur de expression. Contrairement à l'opérateur
dynamic_cast
, l'opérateur static_cast
permet donc d'effectuer
les conversions entre les types autres que les classes définies par l'utilisateur. Aucune vérification
de la validité de la conversion n'a lieu cependant (comme pour le transtypage C classique).
Si une telle expression n'est pas valide, le transtypage peut malgré tout avoir lieu
s'il s'agit d'un transtypage entre classes dérivées et classes de base. L'opérateur
static_cast
permet d'effectuer les transtypages de ce type dans les deux sens
(classe de base vers classe dérivée et classe dérivée vers classe de base). Le transtypage d'une classe
de base vers une classe dérivée ne doit être fait que lorsqu'on est sûr qu'il n'y a pas de danger,
puisqu'aucune vérification dynamique n'a lieu avec static_cast.
Enfin, toutes les expressions peuvent être converties en void avec des qualifications de constance et de volatilité. Cette opération a simplement pour but de supprimer la valeur de l'expression (puisque void représente le type vide).
La suppression des attributs de constance et de volatilité peut être
réalisée grâce à l'opérateur const_cast
. Cet opérateur suit exactement la même
syntaxe que les opérateurs dynamic_cast
et static_cast
:
const_cast<type>(expression)
L'opérateur const_cast
peut travailler
essentiellement avec des références et des pointeurs. Il permet de réaliser les transtypages
dont le type destination est moins contraint que le type source vis-à-vis des mots clés
const et volatile.
En revanche, l'opérateur const_cast
ne permet pas
d'effectuer d'autres conversions que les autres opérateurs de transtypage (ou simplement les transtypages
C classiques) peuvent réaliser. Par exemple, il est impossible de l'utiliser pour convertir un flottant
en entier. Lorsqu'il travaille avec des références, l'opérateur const_cast
vérifie
que le transtypage est légal en convertissant les références en pointeurs et en regardant si
le transtypage n'implique que les attributs const et volatile.
const_cast
ne permet pas de convertir les pointeurs de fonctions.
L'opérateur de transtypage le plus dangereux est
reinterpret_cast
. Sa syntaxe est la même que celle des autres opérateurs
de transtypage dynamic_cast
, static_cast
et
const_cast
:
reinterpret_cast<type>(expression)
Cet opérateur permet de réinterpréter les données d'un type en un autre type. Aucune vérification de la validité de cette opération n'est faite. Ainsi, les lignes suivantes :
double f=2.3; int i=1; const_cast<int &>(f)=i;sont strictement équivalentes aux lignes suivantes :
double f=2.3; int i=1; *((int *) &f)=i;
L'opérateur reinterpret_cast
doit cependant respecter
les règles suivantes :
il ne doit pas permettre la suppression des attributs de constance et de volatilité ;
il doit être symétrique (c'est-à-dire que la réinterprétation d'un type T1 en tant que type T2, puis la réinterprétation du résultat en type T1 doit redonner l'objet initial).
Précédent | Sommaire | Suivant |
Identification dynamique des types | Niveau supérieur | Les espaces de nommage |