web-dev-qa-db-fra.com

Fonction virtuelle C ++ du constructeur

Pourquoi l'exemple suivant imprime "0" et qu'est-ce qui doit changer pour qu'il imprime "1" comme prévu?

#include <iostream>
struct base {
   virtual const int value() const {
      return 0;
   }
   base() {
      std::cout << value() << std::endl;
   }
   virtual ~base() {}
};

struct derived : public base {
   virtual const int value() const {
      return 1;
   }
};

int main(void) {
   derived example;
}
57
Giovanni Funchal

Parce que base est construit en premier et n'a pas encore "mûri" en derived. Il ne peut pas appeler de méthodes sur un objet lorsqu'il ne peut pas garantir que l'objet est déjà correctement initialisé.

90
Sean Bright

Lorsqu'un objet dérivé est en cours de construction, avant d'appeler le corps du constructeur de classe dérivée, le constructeur de classe de base doit se terminer. Avant d'appeler le constructeur de classe dérivée, le type dynamique de l'objet en construction est une instance de classe de base et non une instance de classe dérivée. Pour cette raison, lorsque vous appelez une fonction virtuelle à partir d'un constructeur, seules les remplacements de fonction virtuelle de la classe de base peuvent être appelés.

20
CB Bailey

En fait, il existe un moyen d'obtenir ce comportement. "Chaque problème dans le logiciel peut être résolu avec un niveau d'indirection."

/* Disclaimer: I haven't done C++ in many months now, there might be a few syntax errors here and there. */
class parent
{
public:
     parent( ) { /* nothing interesting here. */ };
protected:
     struct parent_virtual
     {
         virtual void do_something( ) { cout << "in parent."; }
     };

     parent( const parent_virtual& obj )
     {
          obj.do_something( );
     }
};

class child : public parent
{
protected:
     struct child_virtual : public parent_virtual
     {
         void do_something( ) { cout << "in child."; }
     };
public:
      child( ) : parent( child_virtual( ) ) { }
};
9
Tanveer Badar

Vous ne devez pas polymorphiquement appeler les méthodes virtuelles depuis le constructeur. Au lieu de cela, vous pouvez les appeler après la construction de l'objet.

Votre code peut être réécrit comme suit

struct base {
   virtual const int value() const {
      return 0;
   }
   base() {
      /* std::cout << value() << std::endl; */
   }
   virtual ~base() {}
};

struct derived : public base {
   virtual const int value() const {
      return 1;
   }
};

int main(void) {
   derived example;
   std::cout << example.value() << std::endl;
}
4
Vinay

La question de savoir comment cela fonctionne est un FAQ item.

En résumé, pendant que la classe T est en cours de construction, le type dynamique est T, ce qui empêche les appels virtuels aux implémentations de fonctions de classe dérivées qui, si elles étaient autorisées, pouvaient exécuter du code avant que l'invariant de classe pertinent ne soit établi ( un problème courant dans Java et C #, mais C++ est sûr à cet égard).

La question de savoir comment effectuer une initialisation spécifique à une classe dérivée dans un constructeur de classe de base est également a FAQ item, en suivant directement celui mentionné précédemment.

En résumé, l'utilisation d'un polymorphisme statique ou dynamique peut transmettre les implémentations de fonction pertinentes au constructeur (ou classe) de classe de base.

Une façon particulière de le faire est de passer un objet "fabrique de pièces" vers le haut, où cet argument peut être utilisé par défaut. Par exemple, une classe générale Button peut transmettre une fonction API de création de bouton à son constructeur de classe de base Widget, afin que ce constructeur puisse créer l'objet de niveau API correct.

3

La règle générale est que vous n'appelez pas une fonction virtuelle à partir d'un constructeur.

0
Hank Gay