2009-09-15 5 views
4

J'ai une classe de base:moyen sûr pour initialiser une classe dérivée

class CBase { 
    public: 
     virtual void SomeChecks() {} 
     CBase() { 
     /* Do some checks */ 
     SomeChecks(); 
     /* Do some more checks */ 
     } 
}; 

et une classe dérivée:

class CDerived : public CBase { 
    public: 
     virtual void SomeChecks() { /* Do some other checks */ } 
     CDerived() : CBase() {} 
}; 

Cette construction semble être un peu bizarre, mais dans mon cas cela est nécessaire , car CBase effectue certaines vérifications et CDerived peut mélanger des contrôles entre eux. Vous pouvez le voir comme un moyen de "hooker" des fonctions dans le constructeur. Le problème avec cette construction est que lors de la construction de CDerived une base CBase est construite et il n'y a aucune conscience de CDerived (donc la fonction surchargée SomeChecks() n'est pas appelée).

que je pouvais faire quelque chose comme ceci:

class CBase { 
    public: 
     void Init() { 
     /* Do some checks */ 
     SomeChecks(); 
     /* Do some more checks */ 
     } 
     virtual void SomeChecks() {} 
     CBase(bool bDoInit=true) { 
     if (bDoInit) { Init(); } 
     } 
}; 
class CDerived : public CBase { 
    public: 
     virtual void SomeChecks() { /* Do some other checks */ } 
     CDerived() : CBase(false) { Init() } 
}; 

Ce n'est pas vraiment sûr, parce que je veux le constructeur avec le paramètre faux protégé, de sorte que seules les classes dérivées peuvent appeler. Mais alors je vais devoir créer un second constructeur (qui est protégé) et lui faire prendre d'autres paramètres (probablement inutilisés car le constructeur est appelé quand Init() n'a pas besoin d'être appelé).

Donc, je suis assez coincé ici.

EDIT En fait, je veux quelque chose comme ceci:

class CBase { 
    protected: 
     void Init() { /* Implementation of Init ... */ } 
     CBase() { /* Don't do the Init(), it is called by derived class */ } 
    public: 
     CBase() { Init(); }  // Called when an object of CBase is created 
}; 
class CDerived : public CBase { 
    public: 
     CDerived() : CBase() { Init(); } 
}; 

Il me semble qu'il est impossible d'avoir 2 constructeurs avec les mêmes arguments à protéger et que public?

Répondre

3

Appel des méthodes virtuelles dans le constructeur/destructor n'est pas autorisé. Bien que les processus virtuels appellent la version la plus dérivée d'une méthode et que le constructeur n'ait pas terminé, les données les plus dérivées n'ont pas été initialisées correctement, ce qui permet potentiellement d'utiliser une valeur invalide. objet.

Ce que vous cherchez est le modèle de conception de PIMPL:

class CBase { ... }; 
class CDerived: public CBase { ... } 

template<typename T> 
class PIMPL 
{ 
    public: 
     PIMPL() 
      :m_data(new T) 
     { 
      // Constructor finished now do checks. 
      m_data->SomeChecks(); 
     } 
     // Add appropriate copy/assignment/delete as required. 
    private: 
     // Use appropriate smart pointer. 
     std::auto_ptr<T> m_data; 
}; 
int main() 
{ 
    PIMPL<CDerived> data; 
} 
+0

Comme indiqué, l'appel de méthodes virtuelles dans les constructeurs ne conduit pas au comportement souhaité. Je pense que la solution de pimpl est la plus raisonnable. Le sujet est bien couvert dans Scott Meyers Effective C++ book. – count0

+0

En fait, ce n'est pas un pimpl. Au moins, la chose appelée n'est pas. Voir ici: http://en.wikipedia.org/wiki/Opaque_pointer#C.2B.2B – sbi

+0

En fait, c'est. :-) Lisez votre page wiki. Mais un bon livre sur Design Patterns comme GOF serait probablement mieux. –

2

Vous faites quelque chose d'assez bizarre ici.

qui suit fonctionnerait et est tout à fait sûr:

class CBase { 
     void SomeChecks() {}; 
    public: 
     CBase() { 
     /* Do some checks */ 
     SomeChecks(); 
     } 
}; 

class CDerived: public CBase{ 
     void SomeOtherChecks() {}; 
    public: 
     CDerived() { 
     /* Do some other checks */ 
     SomeOtherChecks(); 
     } 
}; 

Dans cette hiérarchie, lorsque CDerived est construit, d'abord CBase effectue ses SomeChecks(), puis CDerived fait ses propres OtherChecks(). Voilà comment il devrait être. Le fait que vous ayez créé un contrôle SomeChecks() montre l'intention de permettre à SomeChecks d'être entièrement remplacé dans les classes dérivées, alors que ces vérifications doivent encore être exécutées dans le constructeur. C'est généralement une indication de l'architecture cassée; en fait, vous essayez de mettre une certaine connaissance de la classe dérivée dans le parent, ce qui est généralement faux.

+0

Ma question était peut-être pas complète. Dans mon cas, le constructeur lit un fichier XML et vérifie les éléments. Une classe dérivée peut ajouter des éléments au XML où la base ne sait rien. Ainsi, lorsque CBase() parcourt les éléments, il transmet les éléments à CDerived :: SomeOtherChecks() pour gérer ces éléments. Je suis d'accord avec la "architecture brisée", une telle chose devrait être évitée. Probablement que je suis en train de tourner dans un plus gros problème que nécessaire ... :-) – To1ne

+2

Pourquoi ne pas inverser cela. Demandez au constructeur dérivé de vérifier les éléments et de transmettre ceux qu'il ne connaît pas à la classe de base. – KeithB

1

Il n'y a pas de solution propre à cela. Vous ne pouvez pas appeler les fonctions CDerived en toute sécurité tant que le corps du CD-ROM n'a pas été entré. À ce moment-là, le ccepteur doit être retourné.

Une solution pourrait être la suivante:

protected: CBase(boost::function<void(*)(CBase*)> SomeChecks) { 
    // Base checks 
    SomeChecks(this); // Checks provieded by derived ctor but running on CBase member. 
} 
3

Ce que vous voulez est appelée construction en deux phases. C++ n'offre pas cela comme une construction syntactcical, donc vous devez le faire par vous-même.

Une façon courante d'y parvenir est d'utiliser l'onguent tout usage du programmeur (ajoutez une autre couche d'indirection): Vous enveloppez vos classes dans une autre classe. Le constructeur de cette classe appelle d'abord le constructeur de votre classe, puis la fonction d'initialisation supplémentaire. Bien sûr, cela chamboule un peu votre design.

0

On devrait concevoir sa hiérarchie de classe de telle manière que seule une classe de base soit responsable de vérifier les contraintes de base. Ce peut être un problème lorsque, par exemple, la classe enfant doit inventer certains arguments constructeur pour le constructeur de la classe parente.

L'autre problème, appelant un autre constructeur avec la même signature d'argument, peut être résolu en utilisant l'astuce 'Constructeur étiqueté': créer un constructeur de modèle.

struct C { 
    enum eConstructor { cCheck, cNoCheck }; 

    template< eConstructor constr = cCheck > C(int i); 

    int positive_value; 
}; 

template<> C::C<C:: cCheck >(int i) : positive_value(std::max(0, i)) { } 
template<> C::C<C::cNoCheck>(int i) : positive_value(i) { } 


struct CFive : public C { 
    CFive(): C<C::cNoCheck>(5) {} 
}; 
-1

Peut-être que cela fonctionne pour vous:

class CBase { 
    public: 
     CBase() { 
     /* Do some checks */ 
     SomeChecks(); 
     /* Do some more checks */ 
     } 
     virtual ~CBase(){} /*don't forget about this*/ 
     virtual void SomeChecks() {} 
}; 


class CDerived : public CBase { 
    public: 
     void SomeChecks() { //without virtual 
      /* Do some other checks */ 
      CBase::SomeChecks(); //if you want checks from CBase 
     } 
     CDerived() : CBase() {} 
}; 

CBase* fromBase = new CBase(); //checking with CBase::SomeChecks() 
CBase* fromDerived = new CDerived(); //checking with CDerived::SomeChecks 
CDerived* alsoDerived = new CDerived(); //checking with CDerived::SomeChecks 
+0

Non, cela ne fonctionnera pas. Si une fonction est 'virtual' dans une classe de base, tous les remplacements dans les classes dérivées sont également' virtual', qu'ils soient marqués comme tels ou non. Et vous ne pouvez pas appeler des fonctions virtuelles à partir de constructeurs ou de destructeurs. (Bien, vous pouvez, mais le résultat est surprenant pour la plupart qui l'essayent.) – sbi

+1

Le fait que j'apprends seulement de mes défauts est évident, parce que je n'ai pas couru dans ce piège C++. Je m'en souviens dans un livre, mais je n'ai jamais pris la peine de le mémoriser. Bon à savoir. – grayasm

+0

@vmihai: Avez-vous regardé "Effective C++" de Scott Meyers? Un bel ensemble de règles (IIRC, 53 dans la dernière édition), bien présenté, bien expliqué, facile à retenir. Je parie que celui de ne pas appeler les fonctions virtuelles des constructeurs et des destructeurs est l'un d'entre eux. – sbi

Questions connexes