2010-04-08 3 views
3

Je cherche un idiome propre C pour la situation suivante:Comment peut-on imposer l'appel d'une fonction de classe de base après un constructeur de classe dérivé?

class SomeLibraryClass { 
    public: 
    SomeLibraryClass() { /* start initialization */ } 
    void addFoo() { /* we are a collection of foos */ } 
    void funcToCallAfterAllAddFoos() { /* Making sure this is called is the issue */ } 
}; 
class SomeUserClass : public SomeLibraryClass { 
    public: 
    SomeUserClass() { 
     addFoo(); 
     addFoo(); 
     addFoo(); // SomeUserClass has three foos. 
    } 
}; 
class SomeUserDerrivedClass : public SomeUserClass { 
    public: 
    SomeUserDerrivedClass() { 
     addFoo(); // This one has four foos. 
    } 
}; 

Alors, ce que je veux vraiment est pour SomeLibraryClass de faire respecter l'appel de funcToCallAfterAllAddFoos à la fin du processus de construction. L'utilisateur ne peut pas le mettre à la fin de SomeUserClass :: SomeUserClass(), ce qui gâcherait SomeUserDerrivedClass. S'il le place à la fin de SomeUserDerrivedClass, il ne sera jamais appelé pour SomeUserClass.

Pour clarifier ce dont j'ai besoin, imaginez que/* start initialization */acquiert un verrou, et funcToCallAfterAllAddFoos() libère un verrou.

Le compilateur sait quand toutes les initialisations pour un objet sont terminées, mais puis-je obtenir à cette information par une bonne astuce?

Répondre

9

Je l'implémenterais probablement avec une usine quelconque. Le code suivant devrait être lu comme pseudocode, je n'ai pas essayé de le compiler ou quoi que ce soit.

class LibraryClass 
{ 
public: 
    template<typename D> 
    static D *GetNewInstance() 
    { 
     // by assigning the new D to a LibraryClass pointer, you guarantee it derives from LibraryClass at compile time 
     // that way, the user can't accidentally type "LibraryClass::GetNewInstance<int>()" and have it work 
     LibraryClass *c = new D(); 
     c->funcToCallAfterAllAddFoos(); 
     return c; 
    } 

    ... 
}; 
+0

+1. C'est la seule réponse ici qui permet au modèle "SomeUserDerrivedClass" de fonctionner (où la classe dérivée hérite du 'Foo's du parent) –

+2

Non, toute approche de passage de constructeur fonctionnerait, vous venez de construire sur la collection passée depuis classes dérivées. – Stephen

+0

Cependant, tous les constructeurs de classes dérivées devraient être public, de sorte qu'il serait possible de contourner accidentellement l'usine. Un moyen de contourner cela? – Thomas

0

Essayez le Non-virtual Interface Idiom. Rendez votre méthode publique non virtuelle, mais appelez-la une méthode virtuelle privée. Les classes dérivées remplacent cette méthode virtuelle privée (oui, les classes dérivées peuvent remplacer les virtuals privés). Placez le verrou dans la méthode publique non virtuelle autour de l'appel de la méthode virtuelle privée. Après avoir examiné le code plus attentivement, je pense que vous feriez mieux d'avoir un constructeur dans la classe de base qui accepte un conteneur d'objets Foo et les ajoute.

+0

Comment cela va-t résoudre le problème? Quelqu'un doit encore appeler la méthode virtuelle lorsque la construction est terminée. – moswald

+0

Non, le seul appelant de la méthode virtuelle est la méthode non virtuelle. Les classes dérivées n'ont pas besoin d'appeler quoi que ce soit. –

+0

Mais alors la méthode virtuelle serait (indirectement) appelée par le constructeur de la classe de base, qui est le * premier * à exécuter, pas le * dernier * ... – Thomas

0

Comme C++ ne permet pas la réflexion, non, vous ne pouvez pas obtenir directement cette information. (Bien qu'il puisse y avoir une certaine façon que j'ignore pour empêcher la compilation de réussir)

Cependant, je questionne la conception ici. Ne serait-il pas plus logique de relâcher le verrou une fois que SomeLibraryClass est terminé? Si vous êtes préoccupé par l'efficacité d'appeler AddFoo plusieurs fois votre bibliothèque peut fournir un membre acceptant un std::vector<Foo> qui aurait seulement à acquérir et libérer le verrou une fois.

+0

Pour clarifier, le verrou doit être maintenu pendant la durée de l'ajout, et non libéré jusqu'à ce que l'objet soit prêt à l'emploi. Libérer le verrou après chaque addFoo ne serait pas seulement inefficace, mais incorrect. –

+0

@Mike Elkins: Je ne dis pas le libérer après chaque AddFoo. Je dis qu'une solution simple est de passer un «vecteur » qui permet à un seul appel de fonction d'ajouter tous les Foos. Ensuite, la fonction qui ajoute des foos peut libérer le verrou lui-même. –

+0

Quant à la méthode qui prend un vecteur, cela peut fonctionner, mais comment facilitez-vous la création de SomeUserDerrivedSubclass? Je dois maintenir le verrou à travers l'appel de SomeUserClass jusqu'à la fin de SomeUserDerrivedSubclass. –

5

Je ne suis pas sûr que ce soit possible. Cependant, vous pouvez redessiner un peu cette question: donner à votre constructeur de la classe de base un argument std::vector<Foo> const &foosToBeAdded, et de laisser les classes dérivées passent les foo corrects s:

class SomeLibraryClass { 
    public: 
    SomeLibraryClass(std::vector<Foo> const &foosToBeAdded) { 
     /* start initialization */ 
     std::for_each(foosToBeAdded.begin(), foosToBeAdded.end(), 
        std::bind1st(std::mem_fun(&SomeLibraryClass::addFoo), this)); 
     funcToCallAfterAllAddFoos(); 
    } 
    private: 
    void addFoo(Foo const &someFoo) { /* we are a collection of foos */ } 
    void funcToCallAfterAllAddFoos() { /* this is now called at the right time */ } 
}; 

class SomeUserClass : public SomeLibraryClass { 
    public: 
    SomeUserClass() : 
     SomeLibraryClass(makeAFooVector()) 
    { 
    } 
    private: 
    std::vector<Foo> makeAFooVector() { /* return a vector with three Foos */ } 
}; 

Le modèle peut être étendu en laissant le constructeur SomeUserClass reçoivent également un vector de Foo s. Il ajoutera ensuite ses propres Foo s à la liste avant d'appeler le constructeur de la classe de base.

Vous pouvez également passer des itérateurs au lieu de vector s. Gauche comme un exercice.

+0

Nitpick mineur: 'std :: for_each' devrait être utilisé à la place de la boucle explicite' for' ici. +1 –

+0

Merci, édité. Je savais à ce sujet, mais je ne l'aurais pas bien fait sans essayer de le compiler d'abord;) – Thomas

+0

Eh bien, franchement, je trouve le 'for' plus explicite que les trucs de liaison ... J'attends lambdas! Quoi qu'il en soit, c'est clairement la meilleure solution, pas de travail autour et aucun risque de créer accidentellement une classe dérivée qui se comporte mal. –

0

Pourquoi fournir une méthode addfoo publique? Vous dites que c'est toute l'initialisation, alors laissez la collection être paassée au constructeur.

Ensuite, vous pouvez appeler le functocall nonvirtual du constructeur

Questions connexes