2010-08-29 3 views
4

je besoin de plusieurs classes C++ pour avoir une méthode statique « registre », mais la mise en œuvre du registre varie entre ces classes.Plusieurs classes C++ doivent utiliser la même méthode statique avec une implémentation différente

Il devrait être statique parce que mon idée est de « registre » toutes ces classes avec Lua (une seule fois bien sûr).

Il est évident que je ne peux pas déclarer une interface avec une fonction virtuelle statique pure. Qu'est-ce que vous me suggérez de faire? La simplicité est la bienvenue, mais je pense qu'une sorte de modèle pourrait fonctionner.

Exemple de ce que je voudrais réaliser

class registerInterface 
{ 
public: 
    static virtual void register() = 0; //obviously illegal 
}; 

class someClass: public registerInterface 
{ 
    static virtual void register() 
    { 
     //I register myself with Lua 
    } 
} 

class someOtherClass: public registerInterface 
{ 
    static virtual void register() 
    { 
     //I register myself with Lua in a different way 

    } 
} 

int main() 
{ 
    someClass::register(); 
    someOtherClass::register(); 

    return 0; 
} 

Répondre

6

Basé sur la façon dont vous avez décrit le problème, on ne sait pas bien pourquoi vous avez besoin, même la « méthode statique virtuelle » sur les classes. Cela devrait être parfaitement légal.

class SomeClass { 
    static void register(void) { 
    ... 
    } 
} 

class SomeOtherClass { 
    static void register(void) { 
    ... 
    } 
} 

int main(int argc, char* argv[]) { 
    SomeClass::register(); 
    SomeOtherClass::register(); 

    return 0; 
} 

Supprimez RegisterInterface, je ne pense pas que vous en ayez besoin.

+0

J'ai pensé à cela, mais ce n'est pas très agréable du point de vue de la conception. Rendons-le un peu plus complexe, et disons que je voudrais laisser un "gestionnaire de registre lua" connaître chaque type de classe qui devrait s'inscrire auprès de Lua, (donc dans l'ensemble, je peux faire "pour chaque classe inscrite, Classe :: register(), en utilisant le polymorphisme) – Goles

+0

@ Mr.Gando: alors choisissez une langue qui est plus agréable du point de vue design ;-) Les classes ne sont pas des objets de première classe en C++, elles ne présentent pas de polymorphisme (une instance de someClass est une instance de registerInterface, mais someClass lui-même n'a pas de type lié au type de registerInterface lui-même), et vous ne pouvez pas vraiment itérer sur les collections de ces classes –

+0

Les classes C++ ne sont pas des objets de première classe C++ n'est pas aussi orienté objet que Ruby ou Objective-C, où vous pourriez avoir une collection d'objets de classe que vous avez parcourus et que vous avez appelée méthodes – Hitesh

2

Si elle aide, vous pouvez prendre la réponse de Hitesh, et d'ajouter:

struct luaRegisterManager { 
    template <typename T> 
    void registrate() { 
     T::registrate(); 
     // do something else to record the fact that we've registered - 
     // perhaps "registrate" should be returning some object to help with that 
    } 
}; 

Puis:

int main() { 
    luaRegisterManager lrm; 
    lrm.registrate<someClass>(); 
    lrm.registrate<someOtherClass>(); 
} 

Plus généralement, si vous voulez introduire un polymorphisme dynamique en C++, alors vous avez besoin un objet, pas seulement une classe. Encore une fois, peut-être que les diverses fonctions register devraient renvoyer des objets, avec une classe de base d'interface commune registeredClass, ou classRegistrationInfo, ou quelque chose du genre.

pourrait-elle fournir un exemple de ce que vous sentez que vous avez besoin polymorphisme dynamique? Le code de Hitesh correspond précisément à votre seul exemple, pour autant que je puisse le voir, de sorte que cet exemple ne doit pas couvrir tous vos cas d'utilisation prévus. Si vous écrivez le code qui l'utilisera, vous saurez peut-être comment l'implémenter, ou peut-être quelqu'un pourra vous conseiller.

Quelque chose d'autre qui pourrait aider:

#include <iostream> 
#include <string> 
#include <vector> 

struct Registered { 
    virtual std::string name() = 0; 
    virtual ~Registered() {} 
    Registered() { 
     all.push_back(this); 
    } 
    static std::vector<Registered*> all; 
}; 

std::vector<Registered*> Registered::all; 
typedef std::vector<Registered*>::iterator Iter; 

template <typename T> 
struct RegisteredT : Registered { 
    std::string n; 
    RegisteredT(const std::string &name) : n(name) { T::registrate(); } 
    std::string name() { return n; } 
    // other functions here could be implemented in terms of calls to static 
    // functions of T. 
}; 

struct someClass { 
    static Registered *r; 
    static void registrate() { std::cout << "registering someClass\n"; } 
}; 
Registered *someClass::r = new RegisteredT<someClass>("someClass"); 

struct someOtherClass { 
    static Registered *r; 
    static void registrate() { std::cout << "registering someOtherClass\n"; } 
}; 
Registered *someOtherClass::r = new RegisteredT<someOtherClass>("someOtherClass"); 

int main() { 
    for (Iter it = Registered::all.begin(); it < Registered::all.end(); ++it) { 
     std::cout << (*it)->name() << "\n"; 
    } 
} 

Il y a toutes sortes de problèmes avec ce code si vous essayez de le diviser entre les unités de compilation multiples. En outre, ce genre de chose conduit à des rapports erronés de détecteurs de fuite de mémoire à moins que vous écrivez aussi du code pour tout détruire à la fin, ou utiliser un vecteur de shared_ptr, vecteur de pointeur Boost, etc Mais vous voyez l'idée générale peut "s'enregistrer", et que vous avez besoin d'un objet pour faire des appels virtuels.

En C++ vous essayez généralement d'éviter l'initialisation statique, bien que, en faveur d'une sorte d'injection setup/dépendance au début de votre programme. Normalement, vous devriez simplement lister toutes les classes qui vous intéressent (en appelant une fonction sur chacune) plutôt que d'essayer de le faire automatiquement.

+0

Ok, votre réponse est très bonne. Ainsi, dans le dernier exemple, vous avez statiquement enregistré ces classes (pré-main), puis itéré sur le vecteur des classes enregistrées. Je peux voir que cela peut effectivement conduire à des problèmes et devrait être évitée. Votre complément à la réponse de Hitesh est également le bienvenu et je pense que c'est ce que j'utiliserai à la fin. Merci beaucoup. – Goles

+0

@ Mr.Gando: J'ai maintenant amélioré mon code d'enregistrement statique pour réduire la quantité de plaque d'immatriculation utilisée par chaque classe pour s'auto-enregistrer statiquement. Cela n'affecte évidemment pas les problèmes fondamentaux de l'initialisation statique. En même temps, mon compilateur m'a rappelé que nous ne pouvons pas avoir une fonction appelée "register" - c'est un mot-clé. –

+0

Je fais quelque chose comme ça tout le temps. J'ajoute une classe * registrar *, qui suit les enregistrables, bien que les instances globales des objets d'usine. Le registrar est construit lors de la première utilisation (dans une fonction static Registrar :: instance()). En surchargeant 'new' et 'delete' dans ces classes pour utiliser un ensemble spécifique de fonctions de tas (malloc(), free() encapsulé dans une DLL spécifique pour plus de simplicité), vous pouvez éviter les problèmes de traverser les DLL, etc. La fonction 'décharger' réduira les faux messages de fuite de mémoire, mais vous aurez probablement encore détecté des fuites à partir des objets d'usine globaux. – darron

1

Vos intentions sont nobles, mais votre solution est Inkling vers « overengineering » (à moins que je manque une solution évidente).

Voici une possibilité: Vous pouvez utiliser le Virtual Friend function idiom Par exemple,

class RegisterInterface{ 
    friend void register(RegisterInterface* x){x->do_real_register();} 
protected: 
    virtual void do_real_register(); 
} 

class Foo : public RegisterInterface{ 
protected: 
    virtual void do_real_register(){} 
}; 

class Bar : public RegisterInterface{ 
protected: 
    virtual void do_real_register(){} 
}; 

int main(int argc, char* argv[]) { 
    BOOST_FOREACH(RegisterInterface* ri, registered_interfaces) 
    { 
    register(ri); 
    } 
    return 0; 
} 
0

Que diriez-vous de cette façon? Définir une classe d'interface:

// IFoobar.h 
class IFoobar{ 
    public: 
     virtual void Register(void) = 0; 
} 

Définissez ensuite la classe qui gère le registre ..

// RegisterFoobar.h 
class RegisterFoobar{ 
    public: 
     // Constructors etc... 
     IFoobar* fooBar; 
     static void RegisterFoobar(IFoobar& fubar){ 
      foobar = &fubar; 
     } 
    private: 
     void Raise(void){ foobar->Register(); } 
} 

Maintenant, puis définissez une autre classe comme celui-ci

// MyFuBar.h 
class MyFuBar : IFoobar{ 
    public: 
     // Constructors etc... 
     void Register(void); 
    private: 
     RegisterFoobar* _regFoobar; 
} 

Appelez le code comme ceci:

//MyFuBar.cpp 
MyFuBar::MyFuBar(){ 
    _regFoobar = new Foobar(); 
    _regFoobar->RegisterFoobar(this); 
} 
void MyFuBar::Register(void){ 
    // Raised here... 
} 

J'ai peut-être mal compris vos exigences ...

1

Je sais que vous avez déjà accepté une réponse, mais je me suis dit que j'écrirais tout de même. Vous pouvez avoir des cours d'auto-enregistrement si vous utilisez une initialisation statique et le CRTP:

#include <vector> 
#include <iostream> 

using namespace std; 

class RegisterableRoot // Holds the list of functions to call, doesn't actually need 
         // need to be a class, could just be a collection of globals 
{ 
    public: 
    typedef void (*registration_func)(); 
    protected: 
    static std::vector<registration_func> s_registery; 
    public: 
    static void do_registration() 
    { 
    for(int i = 0; i < s_registery.size(); ++i) 
     s_registery[i](); 
    } 
    static bool add_func(registration_func func) // returns something so we can use it in 
               // in an initializer 
    { 
    s_registery.push_back(func); 
    return true; 
    } 
}; 



template<typename RegisterableType>   // Doesn't really need to inherit from 
class Registerable : public RegisterableRoot // RegisterableRoot 
{ 
    protected: 
    static const bool s_effect; 
}; 


class A : public Registerable<A> // Honestly, neither does A need to inherit from 
           // Registerable<T> 
{ 
    public: 
    static void Register() 
    { 
    cout << "A" << endl; 
    } 
}; 

class B : public Registerable<B> 
{ 
    public: 
    static void Register() 
    { 
    cout << "B" << endl; 
    } 
}; 

int main() 
{ 

    RegisterableRoot::do_registration(); 
    return 0; 
} 


std::vector<RegisterableRoot::registration_func> RegisterableRoot::s_registery; 

template <typename RegisterableType> // This is the "cute" part, we initialize the 
            // static s_effect so we build the list "magically" 
const bool Registerable<RegisterableType>::s_effect = add_func(&RegisterableType::Register); 

template class Registerable<A>; // Explicitly instantiate the template 
           // causes the equivalent of 
           // s_registery.push_back(&A::Register) to 
           // be executed 
template class Registerable<B>; 

Ce sorties

A 
B 

bien que je ne reposerait pas sur cet ordre si je vous étais. Notez que le template class Registerable<X> n'a pas besoin d'être dans la même unité de traduction que l'appel à do_registration, vous pouvez le mettre avec le reste de votre définition de Foo. Si vous héritez de Registerable<> et que vous n'écrivez pas une fonction static void Register() pour votre classe, vous obtiendrez une erreur de compilation (probablement probablement énigmatique) comme vous pourriez vous y attendre s'il y avait vraiment des "virtual statiques". La "magie" ajoute simplement la fonction spécifique à la classe à la liste à appeler, ce qui évite plusieurs des pièges de l'enregistrement réel dans un initialiseur statique. Vous devez toujours appeler do_registration pour que quelque chose se produise.

+0

C'est en fait très agréable. :), je ne suis pas très compétent avec les modèles C++, pourriez-vous commenter un peu cet exemple de code? Merci! – Goles

+0

@ Mr.Gando J'ai ajouté quelques commentaires et ainsi réalisé qu'un tas de code était superflu. Je l'ai laissé comme c'est pourtant en mentionnant les choses inutiles dans les commentaires. J'espère que cela t'aides. –

+0

Bien sûr que oui, merci Logan. Cette question est devenue très éducative pour moi! – Goles

Questions connexes