2010-10-20 13 views
5

Je dois appeler une méthode virtuelle pour toutes les classes dérivées d'une classe de base de base donnée juste après la construction de l'objet dérivé. Mais le faire dans le constructeur de la classe de base se traduira par un pur appel de méthode virtuelleMéthode virtuelle d'appel immédiatement après la construction

Voici un exemple simplifié:

struct Loader { 
    int get(int index) { return 0; } 
}; 

struct Base{ 
    Base() { 
     Loader l; 
     load(l); // <-- pure virtual call! 
    } 
    virtual void load(Loader &) = 0; 
}; 

struct Derived: public Base { 
    int value; 
    void load(Loader &l) { 
     value = Loader.get(0); 
    } 
}; 

Je peux appeler load au constructeur Derived, mais Derived ne pouvait pas savoir comment pour créer un chargeur. Des idées/solutions de contournement?

+0

Quel est le problème? Vous pouvez appeler une méthode virtuelle pure. –

+2

@Benoit: Pas dans un constructeur. @Vargas: Il peut probablement être mieux conçu, donc vous n'avez pas cette dépendance. Par exemple, pourquoi 'load' est une fonction séparée qui est appelée dans le constructeur? Pourquoi ne pas laisser Derived charger ses propres valeurs. – GManNickG

+0

@Benoit: Du constructeur? !!! C'est ce qu'on appelle le comportement indéfini en C++ –

Répondre

2

Utilisez le modèle PIMPL:

template<typename T> 
class Pimpl 
{ 
    public: 
     Pimpl() 
     { 
      // At this point the object you have created is fully constructed. 
      // So now you can call the virtual method on it. 
      object.load(); 
     } 
     T* operator->() 
     { 
      // Use the pointer notation to get access to your object 
      // and its members. 
      return &object; 
     } 
    private: 
     T object; // Not technically a pointer 
         // But otherwise the pattern is the same. 
         // Modify to your needs. 
}; 

int main() 
{ 
    Pimpl<Derived> x; 
    x->doStuff(); 
} 
+0

Comment puis-je l'utiliser et empêcher Derived d'être créé ailleurs? – Vargas

+1

Rendre le constructeur privé. Et puis faire Pimpl un ami de Derived et Pimpl un ami de Derived2 etc –

+0

Sans avoir besoin de modifier chaque classe dérivée, il y a un moyen? Peut-être en quelque sorte pour faire Pimpl un ami de Base? – Vargas

0

De nombreux frameworks connus (comme MFC) le font: Ils font une fonction-membre (virtuelle) Init() ou Create() et y font l'initialisation puis mandatent dans la documentation que l'utilisateur appelle. Je sais que vous n'aimerez pas cette idée, mais vous ne pouvez pas appeler une méthode virtuelle d'un constructeur et vous attendre à un comportement polymorphe, quelle que soit la pureté des méthodes ...

6

Le problème est que la construction de la classe de base se produit avant que la classe dérivée soit entièrement construite. Vous devez soit appeler « charge » de la classe dérivée, initialise motifs indépendants une fonction de membre virtuel différent ou créer une fonction d'aide pour ce faire:

Base* CreateDerived() 
{ 
    Base* pRet = new Derived; 
    pRet->Load(); 
    return pRet; 
} 
+0

Ok, j'ai réussi à aller aussi loin, mais comment puis-je empêcher Derived d'être créé elsewere, et donc être utilisé sans initialisation correcte? – Vargas

+0

@Vargas: Pour chaque classe dérivée de Base, rendez le constructeur privé et définissez un 'Base * CreateDerivedX()' et faites-en un ami. –

+0

@Eugen, cela peut-il être fait sans avoir besoin de changer chaque classe dérivée? – Vargas

3

Le C++ FAQ appelle ce problème DBDI, Liaison dynamique pendant la construction. Principalement, le problème est d'éviter la construction en deux phases du mal préconisée dans d'autres réponses ici. C'est une sorte de "ma" FAQ - j'ai convaincu Marshall de l'ajouter. Cependant, Marshall l'a pris sur lui est très général (ce qui est bon pour une FAQ), alors que j'étais plus préoccupé par le motif de conception/codage particulier. Ainsi, au lieu de vous envoyer à la FAQ, je vous envoie sur mon propre blog, l'article "How to avoid post-construction by using Parts Factories", qui renvoie à l'article correspondant de la FAQ, mais discute en profondeur du modèle.

Vous pouvez simplement sauter les deux premiers paragraphes ...

je sorte de glosé là-bas. :-)

Vive & HTH.,

1

Pouvez-vous pas ajouter une méthode getLoader() dans votre classe Base pour que DerivedClass constructeur peut l'appeler sur this pour obtenir un Loader? As DerivedClass constructeur sera appelé après Base constructeur de classe, cela devrait fonctionner correctement.

1

Il est difficile de donner des conseils, sauf si vous nous dites ce que vous essayez d'accomplir, plutôt que comment. Je trouve qu'il est généralement préférable de construire de tels objets à partir d'une usine, qui va charger les données requises à l'avance, puis passer les données dans le constructeur de l'objet.

0

Il y a plusieurs façons de corriger cette situation, voici 1 suggestion adapter dans votre cadre fourni

struct Loader { 
    int get(int index) { return 0; } 
}; 

struct Base{ 
    Base() { 
    } 
    Loader & getLoader(); 
private: 
    Loader l; 
}; 

struct Derived: public Base { 
    int value; 
    Derived() { 
     value = getLoader().get(0); 
    } 
}; 
+0

Note: votre constructeur 'Derived' n'est pas déclaré correctement. De même, si l'instance de 'Loader' est coûteuse et ne devrait pas être active trop longtemps (c'est-à-dire qu'elle est lue depuis un fichier XML ou JSON), cela posera un problème. Cependant, bravo pour une solution rétrocompatible! –

+0

Certes, la durée de vie de l'objet Loader change, vous évoquez un bon point sur le coût potentiel de celui-ci. –

0

Cela peut être un peu tard après d'autres réponses, mais je vais donner encore un essai.

Vous peut mettre en œuvre cette en toute sécurité et sans changer les classes dérivées. Cependant, vous devrez changer et utiliser de toutes ces classes, ce qui pourrait être bien pire, selon votre scénario. Si vous concevez toujours, alors ceci pourrait être une alternative viable.

Fondamentalement, vous pouvez appliquer le curiously recurring template pattern et injecter le code d'initialisation après que le constructeur est appelé. En outre, si vous le faites comme je l'ai écrit ci-dessous, vous pouvez même protéger load d'être appelé deux fois. Franchement, cependant, je considérerais un design alternatif si vous le pouvez. Déplacez le code de load à vos constructeurs et de fournir le chargeur comme un argument de référence en défaut comme suit:

struct Derived : public Base { 
    Derived (Loader& loader = Loader()) { ... } 
}; 

De cette façon, vous évitez complètement le problème.

Résumé: vos choix sont les suivants:

  1. Si vous n'êtes pas limité par des contraintes externes et ne dispose pas d'une base de code en fonction de ce vaste, changer votre conception pour quelque chose plus sûr.
  2. Si vous souhaitez conserver load comme il est et ne pas trop modifier vos classes, mais que vous êtes prêt à payer le prix de toutes les instanciations, appliquez CRTP comme proposé ci-dessus.
  3. Si vous insistez pour être principalement rétrocompatible avec le code client existant, vous devrez modifier vos classes pour utiliser un PIMPL comme d'autres l'ont suggéré ou vivre avec le problème existant.
+0

En outre, si vous voulez vous assurer que les objets ne sont pas créés sans être chargés, déclarez les constructeurs comme protégés. Ceci, cependant, n'est pas sûr car les autres clients peuvent simplement dériver de votre classe et faire ce qu'ils veulent, y compris ne pas appeler 'load' ou l'appeler deux fois. –

+0

é: d'abord juste un nit: le 2ème exemple manque d'un 'const' (c'est un code invalide comme présenté). Je suis d'accord avec votre souci de vous débarrasser de la construction en deux phases. Pour une approche plus générale, voir ma réponse précédente. Et/ou la FAQ C++. Salutations & hth., –

+0

@Alf: J'ai quitté le 'const' parce que dans l'énoncé du problème,' ​​Loader :: get() 'n'était pas' const'. Mais vous avez raison, c'est une syntaxe invalide et j'aurais dû le dire de toute façon. En ce qui concerne votre message, je vérifie également votre blog. J'ai utilisé exactement le même design dans le même type de bibliothèque, donc je suis totalement d'accord. J'ai posté ces deux alternatives parce que je trouve qu'elles prennent moins de code à écrire. –