IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

Les fonctions virtuelles en C++ : Types statiques et types dynamiques

Les fonctions virtuelles en C++ :
Types statiques et types dynamiques


précédentsommairesuivant

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.

Types polymorphes :
Sélectionnez
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 :

Comparer des résultats de typeid pour comparer des types :
Sélectionnez
#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 :

typeid retrouve le type dynamique d'objets polymorphes :
Sélectionnez
#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 :

 
Sélectionnez
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 :

typeid retrouve le type statique des objets non polymorphes :
Sélectionnez
#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 :

 
Sélectionnez
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 :

L'expression pour un type non polymorphe n'est pas évaluée :
Sélectionnez
#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 :

L'expression pour un type non polymorphe est évaluée :
Sélectionnez
#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 :

Evaluation implique appel de fonctions :
Sélectionnez
#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 :

 
Sélectionnez
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é :

Avertissement GCC :
Sélectionnez
warning : statement has no effect

XXII-B-3. La classe type_info

Le synopsis de cette classe est assez simple :

Synopsis de la classe type_info
Sélectionnez
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 :

Récupérer le nom d'un type :
Sélectionnez
#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 :

Noms des types avec GCC :
Sélectionnez
i
1A

Et avec Visual C++ :

Noms des types avec Visual C++ :
Sélectionnez
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 :

Avoir des informations sur le type statique :
Sélectionnez
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 :

Les classes traits :
Sélectionnez
#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) :

Exemple de classe trait de la STL :
Sélectionnez
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) :

Exemples de classes traits en C++0x :
Sélectionnez
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 :

L'upcast est automatique :
Sélectionnez
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 :

Les cast à la C : une erreur !
Sélectionnez
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 :

Les casts à la C brisent la vérification de type :
Sélectionnez
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 :

Eviter les erreurs avec les opérateurs de transtypage du langage :
Sélectionnez
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 :

Le downcast static doit être possible :
Sélectionnez
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 :

downcast statique pour les références et les pointeurs :
Sélectionnez
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 :

Le downcast statique peut poser problème :
Sélectionnez
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 :

Le downcast dynamique permet de gérer les problèmes du downcast statique :
Sélectionnez
#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 :

 
Sélectionnez
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 :

Le downcast dynamique d'un pointeur retourne nul plutôt qu'une exception en cas d'erreur :
Sélectionnez
#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 :

 
Sélectionnez
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 :

dynamic_cast fonctionne sur un pointeur nul :
Sélectionnez
#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 :

 
Sélectionnez
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 :

dynamic_cast ne s'utilise qu'avec des types polymorphes :
Sélectionnez
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.


précédentsommairesuivant

Copyright © 2009 3DArchi. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.