XXII. Informations sur les types (dynamiques et statiques) et conversions▲
XXII-A. Types polymorphes▲
Le C++ définit un type polymorphe comme un type contenant au moins une fonction virtuelle ou dont une des classes de bases contient une fonction virtuelle.
struct
base
{
}
;
struct
derived_1 : private
base
{
virtual
void
do_it();
}
;
struct
derived_2 : private
base
{
void
do_it();
}
;
struct
base_2
{
virtual
~
base_2();
}
;
struct
derived_3 : public
base_2
{
}
;
Dans l'exemple précédent, cela donne :
- base : non polymorphe
- derived_1 : polymorphe
- derived_2 : non polymorphe
- base_2 : polymorphe
- derived_3 : polymorphe
L'héritage ne suffit pas pour qu'un type soit polymorphe. Une hiérarchie de classe ne contenant aucune fonction virtuelle n'est pas considérée comme polymorphe. L'explication est facile à comprendre au regard de ce que nous avons vu jusqu'à présent : en l'absence de fonction virtuelle, cela signifie que quelque soit l'expression utilisée, la résolution des appels se fait toujours avec le type statique de l'objet (variable, référence ou pointeur). Il n'est donc pas possible d'invoquer le comportement d'une classe dérivée à partir de la classe mère. En ce sens, il n'y a pas vraiment de polymorphisme car l'objet est toujours vu comme le type statique qui le manipule.
XXII-B. Comment connaître le type dynamique d'une variable ? ▲
XXII-B-1. L'opérateur typeid▲
Pour être utilisé, l'opérateur typeid nécessite le fichier d'en-tête <typeinfo>. L'opérateur typeid retourne un objet de type statique const std::type_info et dont le type dynamique est défini par l'implémentation du compilateur (mais qui a de toute façon const std::type_info comme une de ses classes de base). L'opérateur s'applique aux variables, aux types ainsi qu'aux expressions. Elle permet d'avoir des informations de typage. Les qualificateurs const sont ignorés. Les références sont identiques au type qu'elles désignent et les pointeurs déréférencés aussi. Les valeurs retournées par typeid peuvent être comparées (avec l'opérateur == ou l'opérateur !=), cette comparaison permet de savoir si deux variables/types/expressions sont du même type :
#include
<typeinfo>
#include
<iostream>
struct
my_type
{
}
;
int
main()
{
my_type t1;
my_type t2;
my_type &
rt =
t1;
my_type *
pt =
&
t1;
my_type const
ct=
my_type();
std::
cout<<
std::
boolalpha;
std::
cout<<
"typeid(t1)==typeid(t2) : "
<<
(typeid
(t1)==
typeid
(t2))<<
"
\n
"
;
std::
cout<<
"typeid(t1)==typeid(my_type) : "
<<
(typeid
(t1)==
typeid
(my_type))<<
"
\n
"
;
std::
cout<<
"typeid(t1)==typeid(rt) : "
<<
(typeid
(t1)==
typeid
(rt))<<
"
\n
"
;
std::
cout<<
"typeid(t1)==typeid(my_type&) : "
<<
(typeid
(t1)==
typeid
(my_type&
))<<
"
\n
"
;
std::
cout<<
"typeid(t1)==typeid(pt) : "
<<
(typeid
(t1)==
typeid
(pt))<<
"
\n
"
;
std::
cout<<
"typeid(t1)==typeid(ct) : "
<<
(typeid
(t1)==
typeid
(ct))<<
"
\n
"
;
std::
cout<<
"typeid(t1)==typeid(my_type const) : "
<<
(typeid
(t1)==
typeid
(my_type const
))<<
"
\n
"
;
std::
cout<<
"typeid(t1)==typeid(*pt) : "
<<
(typeid
(t1)==
typeid
(*
pt))<<
"
\n
"
;
std::
cout<<
"typeid(t1)==typeid(my_type*) : "
<<
(typeid
(t1)==
typeid
(my_type*
))<<
"
\n
"
;
std::
cout<<
"typeid(t1)==typeid(int) : "
<<
(typeid
(t1)==
typeid
(int
))<<
"
\n
"
;
return
0
;
}
Appliquer à un type polymorphe, typeid retourne un const std::type_info relatif au type dynamique de l'objet :
#include
<typeinfo>
#include
<iostream>
struct
polymorphic_base
{
virtual
~
polymorphic_base(){}
}
;
struct
polymorphic_derived : public
polymorphic_base
{
}
;
int
main()
{
polymorphic_base b;
polymorphic_derived d;
std::
cout<<
std::
boolalpha;
std::
cout<<
"typeid(b)==typeid(d) : "
<<
(typeid
(b)==
typeid
(d))<<
"
\n
"
;
polymorphic_base &
rb=
b;
std::
cout<<
"typeid(b)==typeid(rb) : "
<<
(typeid
(b)==
typeid
(rb))<<
"
\n
"
;
polymorphic_base &
rd=
d;
std::
cout<<
"typeid(b)==typeid(rd) : "
<<
(typeid
(b)==
typeid
(rd))<<
"
\n
"
;
std::
cout<<
"typeid(d)==typeid(rd) : "
<<
(typeid
(d)==
typeid
(rd))<<
"
\n
"
;
return
0
;
}
Ce code produit comme sortie :
typeid(b)==typeid(d) : false
typeid(b)==typeid(rb) : true
typeid(b)==typeid(rd) : false
typeid(d)==typeid(rd) : true
Le type dynamique sur la référence rd est bien retrouvé et correspond au type polymorphic_derived de d.
Appliquer à un type non polymorphe, l'information de type retournée est relative au type statique :
#include
<typeinfo>
#include
<iostream>
struct
non_polymorphic_base
{
~
non_polymorphic_base(){}
}
;
struct
non_polymorphic_derived : public
non_polymorphic_base
{
}
;
int
main()
{
non_polymorphic_base b;
non_polymorphic_derived d;
std::
cout<<
std::
boolalpha;
std::
cout<<
"typeid(b)==typeid(d) : "
<<
(typeid
(b)==
typeid
(d))<<
"
\n
"
;
non_polymorphic_base &
rb=
b;
std::
cout<<
"typeid(b)==typeid(rb) : "
<<
(typeid
(b)==
typeid
(rb))<<
"
\n
"
;
non_polymorphic_base &
rd=
d;
std::
cout<<
"typeid(b)==typeid(rd) : "
<<
(typeid
(b)==
typeid
(rd))<<
"
\n
"
;
std::
cout<<
"typeid(d)==typeid(rd) : "
<<
(typeid
(d)==
typeid
(rd))<<
"
\n
"
;
return
0
;
}
Ce code donne comme résultat :
typeid(b)==typeid(d) : false
typeid(b)==typeid(rb) : true
typeid(b)==typeid(rd) : true
typeid(d)==typeid(rd) : false
Les deux dernières lignes mettent en évidence que typeid(rd) prend le type statique de la référence non_polymorphic_base et non son type dynamique car non_polymorphic_base n'est pas une classe polymorphe.
XXII-B-2. Evaluation de l'expression▲
Appliqué à un type non polymorphe et utilisant par conséquent le type statique de l'expression, l'opérateur typeid n'évalue pas l'expression à laquelle il est appliqué. En particulier, si un pointeur est nul, et qu'il est déréférencé dans l'expression, cela ne provoque pas d'erreur :
#include
<typeinfo>
#include
<iostream>
struct
non_polymorphic_base
{
~
non_polymorphic_base(){}
}
;
struct
non_polymorphic_derived : public
non_polymorphic_base
{
}
;
int
main()
{
non_polymorphic_base *
pb=
0
;
std::
cout<<
std::
boolalpha;
std::
cout<<
"typeid(*pb)==typeid(non_polymorphic_base) : "
<<
(typeid
(*
pb)==
typeid
(non_polymorphic_base))<<
"
\n
"
;
return
0
;
}
Ce code est correct et ne pose pas de problème à l'exécution.
En revanche, sur un type polymorphe, l'expression est évaluée car c'est le type dynamique obtenu à l'exécution qui est nécessaire à l'opérateur typeid. En particulier, si un pointeur est nul et que celui-ci est déréférencé dans l'expression, une exception de type std::bad_typeid est générée :
#include
<typeinfo>
#include
<iostream>
struct
polymorphic_base
{
virtual
~
polymorphic_base(){}
}
;
struct
polymorphic_derived : public
polymorphic_base
{
}
;
int
main()
{
polymorphic_base *
pb=
0
;
std::
cout<<
std::
boolalpha;
try
{
std::
cout<<
"typeid(*pb)==typeid(polymorphic_base) : "
<<
(typeid
(*
pb)==
typeid
(polymorphic_base))<<
"
\n
"
;
}
catch
(std::
bad_typeid const
&
e_)
{
std::
cout<<
"Erreur : "
<<
e_.what()<<
"
\n
"
;
}
return
0
;
}
Lorsqu'une expression est évaluée, cela signifie aussi que les fonctions qu'elle contient sont effectivement déroulées. Alors que lorsque l'expression n'est pas évaluée, les fonctions ne sont pas exécutées :
#include
<typeinfo>
#include
<iostream>
struct
polymorphic_base
{
virtual
~
polymorphic_base(){}
}
;
struct
polymorphic_derived : public
polymorphic_base
{
}
;
polymorphic_base&
identity_1(polymorphic_base&
r_)
{
std::
cout<<
"Fonction identity_1"
;
return
r_;
}
struct
non_polymorphic_base
{
~
non_polymorphic_base(){}
}
;
struct
non_polymorphic_derived : public
non_polymorphic_base
{
}
;
non_polymorphic_base&
identity_2(non_polymorphic_base&
r_)
{
std::
cout<<
"Fonction identity_2"
;
return
r_;
}
int
main()
{
polymorphic_base b;
std::
cout<<
"typeid(identity_1(b)) : "
;
typeid
(identity_1(b));
std::
cout<<
" --
\n
"
;
non_polymorphic_base b2;
std::
cout<<
"typeid(identity_2(b2)) : "
;
typeid
(identity_2(b2));
std::
cout<<
" --
\n
"
;
return
0
;
}
Ce code donne le résultat :
typeid(identity_1(b)) : Fonction identity_1 --
typeid(identity_2(b2)) : --
Notons que GCC donne un avertissement lorsqu'un l'expression contient un appel non exécuté :
warning : statement has no effect
XXII-B-3. La classe type_info▲
Le synopsis de cette classe est assez simple :
namespace
std {
class
type_info {
public
:
virtual
~
type_info();
bool
operator
==
(const
type_info&
rhs) const
;
bool
operator
!=
(const
type_info&
rhs) const
;
bool
before(const
type_info&
rhs) const
;
const
char
*
name() const
;
private
:
type_info(const
type_info&
rhs);
type_info&
operator
=
(const
type_info&
rhs);
}
;
}
type_info est dans l'espace de nom
std. Le constructeur par copie et
l'opérateur d'affectation étant privés, les objets retournés
par typeid ne sont pas copiables. Il n'est
donc pas possible de garder par valeur le retour
de typeid dans un autre objet ou dans
un conteneur. En revanche, on peut garder une référence
ou un pointeur sur l'objet type_info
retourné, la norme garantit que la durée de vie de cet
objet doit dépasser celle du programme (pas du processus !).
type_info::name() retourne une chaîne
de caractère terminée par un '\0' contenant le nom du
type. Ce nom est spécifique au compilateur :
#include
<typeinfo>
#include
<iostream>
class
A{}
;
int
main()
{
std::
cout<<
typeid
(int
).name()<<
"
\n
"
;
std::
cout<<
typeid
(A).name()<<
"
\n
"
;
return
0
;
}
Produit comme sortie avec GCC :
i
1
A
Et avec Visual C++ :
int
class
A
type_info::before() permet de définir un ordre sur les objets retournés. Cet ordre est spécifique au compilateur.
XXII-B-4. Pourquoi récupérer le type dynamique ?▲
Il est fort à parier que si vous ressentez le besoin de récupérer le type dynamique d'une variable ou d'une expression, c'est que vous avez probablement une erreur de conception de votre logiciel. En revanche, typeid peut être utile en mode debug et pour le log afin d'avoir des traces sur les objets effectivement manipulés. En dehors de ces cas de debug et de tests-U, rares sont les bonnes raisons d'avoir à le faire.
XXII-C. Comment connaître le type statique d'une variable ?▲
Cette question peut sembler incongrue puisque nous avons toujours dit et vu que le type statique d'une variable est celui que nous avons sous le nez en lisant le code. Ecartons le typedef qui ne définit qu'un synonyme d'un type et dont on peut retrouver facilement le type effectivement désigné. Avec les fonctions et les classes génériques (template) le type est un paramètre et ne peut donc être connu immédiatement lors de son utilisation :
template
<
class
T>
void
function(T const
&
t_)
{
// quelles informations sur le type T ?
}
Si l'utilisation de template suppose que justement le type n'a pas d'importance, en pratique, on souhaite parfois avoir des informations sur le type effectivement utilisé pour instancier la fonction ou la classe générique. Pour cela, on s'appuie sur des classes traits. Les classes traits utilisent le mécanisme des classes génériques et des spécialisations pour permettre d'obtenir des informations sur les types :
#include
<iostream>
template
<
class
T>
struct
is_int
{
static
const
bool
value =
false
;
}
;
template
<>
struct
is_int<
int
>
{
static
const
bool
value =
true
;
}
;
template
<
class
T>
void
is_an_int(T const
&
)
{
std::
cout<<
is_int<
T>
::
value<<
"
\n
"
;
}
class
A{}
;
int
main()
{
int
i;
A a;
double
d;
std::
cout<<
std::
boolalpha;
std::
cout<<
"is_an_int(i) ? "
;
is_an_int(i);
std::
cout<<
"is_an_int(a) ? "
;
is_an_int(a);
std::
cout<<
"is_an_int(d) ? "
;
is_an_int(d);
return
0
;
}
Les classes traits sont beaucoup utilisées dans la programmation
générique. Vous pouvez trouver une introduction aux classes
traits et aux classes de politiques dans : Présentation des classes de Traits et de Politiques en C++Présentation des classes de Traits et de Politiques en C++,
par Alp Mestan.
Actuellement, le C++ possède quelques classes traits dans
la STL. Par exemple, la bibliothèque string
contient des classes traits sur les types de caractères pour
définir des propriétés sur ceux-ci :
template<class charT> char_traits<charT>
est spécialisée avec char et wchar_t.
Cela permet de n'avoir qu'une seule classe std::basic_string
et de s'appuyer sur ces classes traits pour définir les chaînes
ANSI (std::string) ou UNICODE
(std::wstring) :
namespace
std {
template
<
typename
_CharT, typename
_Traits, typename
_Alloc>
class
basic_string;
typedef
basic_string<
char
,char_traits<
char
>
>
string;
typedef
basic_string<
wchar_t
,char_traits<<
wchar_t
>
>
wstring;
}
La bibliothèque Boost.Type TraitsBoost.Type Traits propose beaucoup de classes traits permettant d'avoir un nombre appréciable d'informations.
C++0x : la nouvelle norme prévoir d'intégrer une collection plus riche de classes traits. Beaucoup des ces classes qui nécessitent aujourd'hui une bibliothèque comme Boost.Type Traits seront avec les compilateurs conformes à cette nouvelle norme directement disponibles avec le fichier d'en-tête #include <type_traits>. Voici quelques exemples tirés du draft N3000 de septembre 2009 (donc encore susceptible d'évoluer) :
namespace
std {
template
<
class
T>
struct
is_void;
template
<
class
T>
struct
is_integral;
template
<
class
T>
struct
is_floating_point;
template
<
class
T>
struct
is_array;
template
<
class
T>
struct
is_pointer;
template
<
class
T>
struct
is_class;
template
<
class
T>
struct
is_const;
template
<
class
T>
struct
is_polymorphic;
template
<
class
T>
struct
is_abstract;
// etc.
}
XXII-D. Conversions entre type de base et type dérivé▲
XXII-D-1. Conversion du type dérivé vers le type de base▲
La conversion depuis le type dérivé vers le type de base (upcast) est automatique en C++ dès lors que l'héritage est public. Nous l'avons employé dans chacun des exemples précédents sans que cela pose le moindre problème :
struct
base
{
virtual
~
base(){}
}
;
struct
derived : public
base
{
}
;
int
main()
{
derived d;
base b =
d;
base b2;
b2 =
d;
base &
rb =
d;
base *
pb =
&
d;
return
0
;
}
base b = d; suppose que
base est constructible par copie. En
effet cette ligne appel le constructeur de copie de
base sur la nouvelle variable
b avec en paramètre d.
b2 = d; suppose que
base est assignable. En
effet cette ligne appel l'opérateur d'affectation
base sur la variable b2
avec en paramètre d.
Les deux autres lignes base &rb = d;
et base *pb = &d; ne supposent
rien sur le type base et définissent
une référence (respectivement un pointeur) dont
le type statique est base et le type
dynamique derived.
La conversion depuis un type dérivé vers un type de base
ne pose strictement aucun problème. S'il s'agit
de pointeurs ou de références alors la nouvelle variable
pointe/référence un objet du type dérivé au travers
d'un pointeur/référence de type statique de base. Si
la conversion va vers une valeur, bien sûr l'information
du type dérivé est perdue puisqu'un nouvel objet du
type de base est construit avec uniquement les
informations du type de base contenu dans l'objet
dérivée.
XXII-D-2. Conversion du type de base vers le type dérivé▲
Si vous venez du monde du C ou que vous avez eu un cours mal appelé C/C++, vous aurez peut être le réflexe de faire ça :
struct
base
{
virtual
~
base(){}
}
;
struct
derived : public
base
{
}
;
int
main()
{
derived d;
base &
rb =
d;
derived&
rd =
(derived&
)d;
return
0
;
}
Oubliez tout de suite ce genre de conversion car un jour vous aurez ça :
struct
base
{
virtual
~
base(){}
}
;
struct
derived : public
base
{
}
;
int
main()
{
derived d;
int
i;
derived&
rd =
(derived&
)i;
return
0
;
}
Code qui compile parfaitement mais qui emmènera votre
application directement dans le mur.
Le C++ propose un premier opérateur de conversion entre
types compatibles : static_cast. Cet
opérateur vous préserve de l'erreur précédente :
struct
base
{
virtual
~
base(){}
}
;
struct
derived : public
base
{
}
;
int
main()
{
derived d;
int
i;
derived&
rd =
static_cast
<
derived&>
(i);
// 'static_cast' : impossible de convertir de 'int' en 'derived &'
return
0
;
}
Utilisé avec une valeur, l'opérateur static_cast<T>(e) est valide si la déclaration T t_(e) est valide. Autrement dit, la conversion réussie s'il existe un constructeur prenant un argument du type de e ou d'un type pouvant être automatiquement converti depuis e. Donc, en général, vouloir affecter à une classe dérivée un objet d'une classe de base provoque une erreur ce qui est bien normale :
struct
base
{
virtual
~
base(){}
}
;
struct
derived : public
base
{
}
;
int
main()
{
base b;
derived d =
static_cast
<
derived>
(b);
// 'static_cast' : impossible de convertir de 'base' en 'derived'
return
0
;
}
La conversion d'un type de base vers un type dérivé (downcast) peut être intéressante si le type dynamique du type de base correspond au type dérivé cible. Cela peut donc se faire avec les références et les pointeurs :
struct
base
{
virtual
~
base(){}
}
;
struct
derived : public
base
{
}
;
int
main()
{
derived d;
base &
rb =
d;
derived &
rd =
static_cast
<
derived&>
(rb); // rd désigne maintenant d
return
0
;
}
L'opérateur static_cast ne peut pas s'appliquer si derived hérite virtuellement de base. Le compilateur signale une erreur.
A noter que si le type dynamique de rb n'est pas derived (ou une classe dérivant de derived), la conversion est indéterminée, en d'autres termes c'est une erreur d'exécution. Cette erreur est silencieuse et le plantage peut avoir lieu à n'importe quel moment, en particulier très loin après le cast malheureux :
struct
base
{
virtual
~
base(){}
}
;
struct
derived : public
base
{
}
;
struct
derived2 : public
base
{
}
;
int
main()
{
derived d;
base b;
base &
rb =
b;
derived &
rd =
static_cast
<
derived&>
(rb);
// OK à la compilation mais
// erreur à l'exécution car rb désigne un base (b) et non un derived
derived2 d2;
base &
rb2 =
d2;
derived &
rd2 =
static_cast
<
derived&>
(rb2);
// OK à la compilation mais
// erreur à l'exécution car rb2 désigne un derived_2 (d2) et non un derived
return
0
;
}
Pour pallier à ces défauts, un autre opérateur existe en C++ : dynamic_cast. Il fonctionne de façon semblable à static_cast si ce n'est qu'il vérifie à l'exécution que le type dynamique de l'expression peut être converti vers le type demandé. Comme le type dynamique est sollicité, dynamic_cast ne s'applique qu'aux pointeurs et aux références. Si la conversion échoue avec une référence, alors l'opérateur soulève une exception std::bad_cast définie dans le fichier typeinfo. Cette exception peut ainsi être récupérée et traitée :
#include
<typeinfo>
#include
<iostream>
struct
base
{
virtual
~
base(){}
}
;
struct
derived : public
base
{
}
;
struct
derived2 : public
base
{
}
;
int
main()
{
derived d;
base &
rb_1 =
d;
try
{
std::
cout<<
"dynamic_cast<derived&>(base &rb_1 = derived d) : "
;
derived &
rd =
dynamic_cast
<
derived&>
(rb_1);
std::
cout<<
"Conversion valide
\n
"
;
}
catch
(std::
bad_cast const
&
e_){
std::
cout<<
"Erreur de dynamic_cast : "
<<
e_.what()<<
std::
endl;
}
base b;
base &
rb_2 =
b;
try
{
std::
cout<<
"dynamic_cast<derived&>(base &rb_2 = base b) : "
;
derived &
rd =
dynamic_cast
<
derived&>
(rb_2);
std::
cout<<
"Conversion valide
\n
"
;
}
catch
(std::
bad_cast const
&
e_){
std::
cout<<
"Erreur de dynamic_cast : "
<<
e_.what()<<
std::
endl;
}
derived2 d2;
base &
rb_3 =
d2;
try
{
std::
cout<<
"dynamic_cast<derived&>(base &rb_3 = derived2 d2) : "
;
derived &
rd =
dynamic_cast
<
derived&>
(rb_3);
std::
cout<<
"Conversion valide
\n
"
;
}
catch
(std::
bad_cast const
&
e_){
std::
cout<<
"Erreur de dynamic_cast : "
<<
e_.what()<<
std::
endl;
}
return
0
;
}
Ce code produit la sortie suivante :
dynamic_cast<derived&>(base &rb_1 = derived d) : Conversion valide
dynamic_cast<derived&>(base &rb_2 = base b) : Erreur de dynamic_cast : std::bad_cast
dynamic_cast<derived&>(base &rb_3 = derived2 d2) : Erreur de dynamic_cast : std::bad_cast
Avec les pointeurs, l'erreur n'est pas remontée avec une exception mais en retournant un pointeur nul :
#include
<iostream>
struct
base
{
virtual
~
base(){}
}
;
struct
derived : public
base
{
}
;
struct
derived2 : public
base
{
}
;
int
main()
{
derived d;
base *
pb_1 =
&
d;
std::
cout<<
"dynamic_cast<derived*>(base *pb_1 = derived d) : "
;
derived *
pd_1 =
dynamic_cast
<
derived*>
(pb_1);
if
(pd_1){
std::
cout<<
"Conversion valide
\n
"
;
}
else
{
std::
cout<<
"Echec de la conversion
\n
"
;
}
base b;
base *
pb_2 =
&
b;
std::
cout<<
"dynamic_cast<derived*>(base *pb_2 = base b) : "
;
derived *
pd_2 =
dynamic_cast
<
derived*>
(pb_2);
if
(pd_2){
std::
cout<<
"Conversion valide
\n
"
;
}
else
{
std::
cout<<
"Echec de la conversion
\n
"
;
}
derived2 d2;
base *
pb_3 =
&
d2;
std::
cout<<
"dynamic_cast<derived*>(base *pb_3 = derived2 d2) : "
;
derived *
pd_3 =
dynamic_cast
<
derived*>
(pb_3);
if
(pd_3){
std::
cout<<
"Conversion valide
\n
"
;
}
else
{
std::
cout<<
"Echec de la conversion
\n
"
;
}
return
0
;
}
Le résultat obtenu :
dynamic_cast<derived*>(base *pb_1 = derived d) : Conversion valide
dynamic_cast<derived*>(base *pb_2 = base b) : Echec de la conversion
dynamic_cast<derived*>(base *pb_3 = derived2 d2) : Echec de la conversion
Appliqué à un pointeur nul, dynamic_cast retourne un pointeur nul :
#include
<iostream>
struct
base
{
virtual
~
base(){}
}
;
struct
derived : public
base
{
}
;
int
main()
{
base *
pb_1 =
0
;
std::
cout<<
"dynamic_cast<derived*>(base *pb_1 = 0) : "
;
derived *
pd_1 =
dynamic_cast
<
derived*>
(pb_1);
if
(pd_1){
std::
cout<<
"Conversion valide
\n
"
;
}
else
{
std::
cout<<
"Echec de la conversion
\n
"
;
}
return
0
;
}
La sortie est :
dynamic_cast<derived*>(base *pb_1 = 0) : Echec de la conversion
dynamic_cast ne peut s'appliquer que sur une expression dont le type est polymorphique :
struct
polymorphic_base
{
virtual
~
polymorphic_base(){}
}
;
struct
polymorphic_derived : public
polymorphic_base
{
}
;
struct
non_polymorphic_base
{
~
non_polymorphic_base(){}
}
;
struct
non_polymorphic_derived : public
non_polymorphic_base
{
}
;
int
main()
{
polymorphic_derived pd;
polymorphic_base &
rpd =
pd;
polymorphic_derived &
rpdd =
dynamic_cast
<
polymorphic_derived &>
(rpd);// OK
non_polymorphic_derived npd;
non_polymorphic_base &
rnpd =
npd;
non_polymorphic_derived &
rnpdd =
dynamic_cast
<
non_polymorphic_derived &>
(rnpd);
// Erreur de compilation : 'non_polymorphic_base' n'est pas un type polymorphe
return
0
;
}
Enfin, dynamic_cast échoue si l'héritage n'est pas public (héritage privée ou protégé) ou s'il est ambigüe (héritage multiple).
XXII-D-3. Pourquoi faire une conversion d'un type de base vers un type dérivé▲
95% des cas où vous avez besoin de convertir un pointeur ou une référence depuis une classe de base vers un type dérivé traduisent une erreur de conception. Je ne parlerai pas des 5% restant pour ne pas semer le trouble. Si le downcast vous titille, visiter le forum C++ pour confirmer votre besoin ou refroidir vos ardeurs.