Héritage et utilisation de dynamic_cast

Supposons que j’ai 3 classes comme suit (comme c’est un exemple, ça ne comstackra pas!):

class Base { public: Base(){} virtual ~Base(){} virtual void DoSomething() = 0; virtual void DoSomethingElse() = 0; }; class Derived1 { public: Derived1(){} virtual ~Derived1(){} virtual void DoSomething(){ ... } virtual void DoSomethingElse(){ ... } virtual void SpecialD1DoSomething{ ... } }; class Derived2 { public: Derived2(){} virtual ~Derived2(){} virtual void DoSomething(){ ... } virtual void DoSomethingElse(){ ... } virtual void SpecialD2DoSomething{ ... } }; 

Je souhaite créer une instance de Derived1 ou Derived2 en fonction de certains parameters qui ne sont disponibles qu’au moment de l’exécution.

Comme je ne peux pas déterminer le type dérivé jusqu’au moment de l’exécution, pensez-vous que la pratique suivante est une mauvaise pratique? …

 class X { public: .... void GetConfigurationValue() { .... // Get configuration setting, I need a "Derived1" b = new Derived1(); // Now I want to call the special DoSomething for Derived1 (dynamic_cast(b))->SpecialD1DoSomething(); } private: Base* b; }; 

J’ai généralement lu que l’utilisation de dynamic_cast est mauvaise, mais comme je l’ai dit, je ne sais pas quel type créer avant le moment de l’exécution. S’il vous plaît aider!

Utiliser dynamic_cast n’est pas une mauvaise pratique en soi. C’est une mauvaise pratique de l’utiliser de manière inappropriée, c’est-à-dire là où ce n’est pas vraiment nécessaire.

C’est aussi une mauvaise pratique de l’utiliser de cette façon:

 (dynamic_cast(b))->SpecialD1DoSomething(); 

Raison: dynamic_cast (b) peut renvoyer NULL.

Si vous utilisez dynamic_cast, vous devez être extrêmement prudent, car ce n’est pas garanti, que b est en fait de type Derived1 et non Derived2:

 void GenericFunction(Base* p) { (dynamic_cast(b))->SpecialD1DoSomething(); } void InitiallyImplementedFunction() { Derived1 d1; GenericFunction(&d1); // OK... But not for long. // Especially, if implementation of GenericFunction is in another library // with not source code available to even see its implementation // -- just headers } void SomeOtherFunctionProbablyInAnotherUnitOfCompilation() { Derived2 d2; GenericFunction(&d2); // oops! } 

Vous devez vérifier si dynamic_cast réussit réellement. Il y a deux façons de le faire: le vérifier avant et après la dissortingbution. Avant le casting, vous pouvez vérifier si le pointeur que vous essayez de lancer est bien celui que vous attendez via RTTI:

 if (typeid(b) == typeid(Derived1*)) { // in this case it's safe to call the function right // away without additional checks dynamic_cast(b)->SpecialD1DoSomething(); } else { // do something else, like try to cast to Derived2 and then call // Derived2::SpecialD2DoSomething() in a similar fashion } 

La vérification post-factum est en réalité un peu plus simple:

 Derived1* d1 = dynamic_cast(b); if (d1 != NULL) { d1->SpecialD1DoSomething(); } 

Je dirais également que c’est une mauvaise pratique d’essayer de sauvegarder les saisies lors de la programmation en C ++. Il y a beaucoup de fonctionnalités en C ++ qui semblent être parfaitement correctes pour être tapées plus courtes (c’est-à-dire qui vous donne l’impression ‘que NULL ne se produira jamais ici’), mais s’avèrent être une douleur pour le débogage après. 😉

Pourquoi ne pas retarder le moment où vous “jetez” certains si le type d’information en assignant un pointeur à dérivé à un pointeur à base:

 void GetConfigurationValue() { // ... // Get configuration setting, I need a "Derived1" Derived1* d1 = new Derived1(); b = d1; // Now I want to call the special DoSomething for Derived1 d1->SpecialD1DoSomething(); } 

Le point essentiel des fonctions virtuelles est qu’une fois que vous avez le bon type d’object, vous pouvez appeler la bonne fonction sans savoir de quelle classe dérivée cet object correspond. Vous appelez simplement la fonction virtuelle et il fait ce qui est bien.

Vous n’avez besoin d’une dynamic_cast lorsque vous avez une classe dérivée qui définit quelque chose de différent qui n’est pas présent dans la classe de base, et vous devez / voulez prendre en compte quelque chose en plus.

Par exemple:

 struct Base { virtual void do_something() {} }; struct Derived : Base { virtual void do_something() {} // override dosomething virtual void do_something_else() {} // add a new function }; 

Maintenant, si vous voulez simplement appeler do_something() , une do_something() est totalement inutile. Par exemple, vous pouvez avoir une collection de Base * et simplement invoquer do_something() sur tout le monde, sans faire attention au fait que l’object soit vraiment une Base ou un Derived .

Lorsque / si vous avez une Base * et que vous souhaitez invoquer do_something_else() , vous pouvez utiliser un dynamic_cast pour déterminer si l’object lui-même est vraiment un object Derived afin de pouvoir l’invoquer.

Autres éléments à prendre en compte pour éviter l’utilisation de dynamic_cast

De Effective C ++ (Troisième édition) – Article 35 Alternatives aux fonctions virtuelles –

  1. «Modèle de méthode de modèle» via une interface non vitale (NVI). Rendre les fonctions virtuelles privées / protégées avec une méthode publique ‘wrapper’ – vous permet d’appliquer un autre stream de travail de tâches à effectuer avant et après la méthode virtuelle.
  2. «Modèle de stratégie» via les pointeurs de fonction. Transmettez la méthode supplémentaire en tant que pointeur de fonction.
  3. ‘Modèle de stratégie’ via tr1 :: function. semblable à 2. mais vous pouvez fournir des classes entières avec diverses options
  4. «Modèle de stratégie» classique. Séparez la stratégie de la classe principale – placez les fonctions virtuelles dans une autre hiérarchie.

Il existe un modèle appelé Modèle d’usine qui convient à ce scénario. Cela vous permet de renvoyer une instance de la classe correcte en fonction de certains parameters d’entrée.

Prendre plaisir!

Qu’est-ce qui ne va pas chez:

 Base * b; if( some_condition ) { b = new Derived1; } else { b = new Derived2; } if ( Derived2 * d2 = dynamic_cast ( b ) ) { d2->SpecialD2DoSomething(); } 

Ou est-ce que je manque quelque chose?

Et puis-je suggérer que lorsque vous posez des questions comme celle-ci, vous (et d’autres) nommez vos classes A, B, C, etc., ainsi que vos fonctions, comme f1 (), f2 (), etc. Cela simplifie grandement la vie des personnes qui répondent à vos questions .

Une façon d’éviter dynamic_cast est d’avoir une fonction de trampoline virtuelle “SpecialDoSomething” dont l’implémentation polymorphe dérivée appelle “SpecialDxDoSomething ()” de cette classe dérivée particulière, qui peut être n’importe quel nom de classe non base que vous désirez. Il peut même appeler plus d’une fonction.