2010-05-17 2 views
11

registrieren Wenn eine MessageFactory Klasse Umsetzung Message-Objekte I wie etwas verwendet, um instatiate:Dynamisch Konstruktormethoden in einem Abstract bei der Kompilierung mit C++ Vorlagen

class MessageFactory 
{ 
    public: 
    static Message *create(int type) 
    { 
     switch(type) { 
     case PING_MSG: 
      return new PingMessage(); 
     case PONG_MSG: 
      return new PongMessage(); 
     .... 
    } 
} 

Dies funktioniert ok, aber jedes Mal, wenn ich hinzufügen, eine neue Nachricht Ich habe um eine neue XXX_MSG hinzuzufügen und die switch-Anweisung zu ändern.

Nach einigen Nachforschungen fand ich eine Möglichkeit, die MessageFactory während der Kompilierung dynamisch zu aktualisieren, so dass ich beliebig viele Nachrichten hinzufügen kann, ohne die MessageFactory selbst ändern zu müssen. Dies ermöglicht saubere und einfachen Code zu halten, wie ich brauche, um Nachrichtenklassen nicht zu ändern drei verschiedene Orte hinzufügen/entfernen: durch die Registrierung in die MessageFactory Klasse, alle neue Meldeklassen

#include <stdio.h>                                           
#include <stdlib.h>                                           
#include <string.h>                                           
#include <inttypes.h>                                           

class Message                                             
{                                                
    protected:                                             
     inline Message() {};                                         

    public:                                             
     inline virtual ~Message() { }                                       
     inline int getMessageType() const { return m_type; }                                 
     virtual void say() = 0;                                         

    protected:                                             
     uint16_t m_type;                                          
};                                               

template<int TYPE, typename IMPL>                                        
class MessageTmpl: public Message                                        
{                                                
    enum { _MESSAGE_ID = TYPE };                                        
    public:                                             
     static Message* Create() { return new IMPL(); }                                   
     static const uint16_t MESSAGE_ID; // for registration                                 

    protected:                                             
     MessageTmpl() { m_type = MESSAGE_ID; } //use parameter to instanciate template                           
};                                               

typedef Message* (*t_pfFactory)();                                       
class MessageFactory⋅                                           
{                                                
    public:                                             
    static uint16_t Register(uint16_t msgid, t_pfFactory factoryMethod)                              
    {                                              
     printf("Registering constructor for msg id %d\n", msgid);                                
     m_List[msgid] = factoryMethod;                                       
     return msgid;                                           
    }                                              

    static Message *Create(uint16_t msgid)                                     
    {                                              
     return m_List[msgid]();                                        
    }                                              
    static t_pfFactory m_List[65536];                                      
}; 

template <int TYPE, typename IMPL>                                       
const uint16_t MessageTmpl<TYPE, IMPL >::MESSAGE_ID = MessageFactory::Register(                            
    MessageTmpl<TYPE, IMPL >::_MESSAGE_ID, &MessageTmpl<TYPE, IMPL >::Create);                            

class PingMessage: public MessageTmpl < 10, PingMessage >                                  
{⋅                                               
    public:                                              
    PingMessage() {}                                           
    virtual void say() { printf("Ping\n"); }                                     
};                                               

class PongMessage: public MessageTmpl < 11, PongMessage >                                  
{⋅                                               
    public:                                              
    PongMessage() {}                                           
    virtual void say() { printf("Pong\n"); }                                     
};                                               

t_pfFactory MessageFactory::m_List[65536];                                     

int main(int argc, char **argv)                                        
{                                                
    Message *msg1;                                            
    Message *msg2;                                            

    msg1 = MessageFactory::Create(10);                                       
    msg1->say();                                            

    msg2 = MessageFactory::Create(11);                                       
    msg2->say();                                            

    delete msg1;                                            
    delete msg2;                                            

    return 0;                                             
} 

Die Vorlage hier funktioniert die Magie (zB PingMessage und PongMessage) diese Unterklasse von MessageTmpl.

Dies funktioniert gut und vereinfacht die Wartung Code aber ich habe noch einige Fragen zu dieser Technik haben:

  1. Ist das eine bekannte Technik/Muster? wie heißt? Ich möchte mehr Informationen darüber suchen.

  2. Ich möchte zum Speichern neuer Konstrukteure das Array machen MessageFactory :: m_List [65536] ein std :: map aber dabei wird das Programm selbst segfault vor Haupt erreichen(). Erstellen eines Arrays von 65536 Elementen ist Overkill, aber ich habe keinen Weg gefunden, dies zu einem dynamischen Container zu machen.

  3. Für alle Nachrichtenklassen, die Unterklassen von MessageTmpl sind, muss ich den Konstruktor implementieren. Wenn nicht, wird es nicht in der MessageFactory registriert.

    Zum Beispiel den Konstruktor der PongMessage Kommentierung:

    class PongMessage: public MessageTmpl < 11, PongMessage >  
    {                                               
        public:                                              
        //PongMessage() {} /* HERE */                                           
        virtual void say() { printf("Pong\n"); }     
    }; 
    

    in der PongMessage Klasse würde nicht von der MessageFactory registriert wird und das Programm würde segfault im MessageFactory :: Create (11) Linie . Die Frage ist
    warum die Klasse nicht registriert? Die leere Implementierung der 100+ Nachrichten, die ich brauche hinzuzufügen, fühlt sich ineffizient und unnötig an.

+0

# 1 ist CRTP (Sorten) http://en.wikipedia.org/wiki/Curiously_recurring_template_pattern – Anycorn

+0

# 3, weil Konstruktor MessageTmpl geschützt ist (vielleicht) – Anycorn

+0

durch die Art und Weise, überprüfen Sie Ihre Codeliste. es hat Streuner; und . Figuren. Ich habe es kompiliert, aber ich bekomme einen Segmentierungsfehler. – Anycorn

Antwort

7

Antwort One

Die allgemeine Technik, um eine Klasse wie folgt abzuleiten ist die Curiously Recurring Template Pattern (CRTP):

class PingMessage: public MessageTmpl < 10, PingMessage > 

Ihre spezielle Technik einer Template-Klasse statische Memberinitialisierungsliste mit Subklassen registrieren dieser Klasse ist (IMO) einfach genial, und das habe ich noch nie zuvor gesehen. Ein üblicherer Ansatz, der von Unit-Test-Frameworks wie UnitTest++ und Google Test verwendet wird, besteht darin, Makros bereitzustellen, die sowohl eine Klasse als auch eine separate statische Variable deklarieren, die diese Klasse initialisiert.

Antwort Zwei

Statische Variablen werden in der Reihenfolge aufgeführt, initialisiert. Wenn Sie Ihre m_List-Deklaration vor Ihren MessageFactory :: Register-Aufrufen verschieben, sollten Sie sicher sein. Beachten Sie auch, dass Sie m_List als Singleton umbrechen müssen, wenn Sie in mehr als einer Datei Nachrichtenunterklassen deklarieren, und überprüfen Sie vor jeder Verwendung, dass sie aufgrund der C++ static initialization order fiasco initialisiert werden.

Antwort Drei

C++ Compiler nur Template Mitglieder instanziiert, die tatsächlich verwendet werden. Statische Member von Template-Klassen sind kein Bereich von C++, den ich sehr oft benutzt habe, daher könnte ich hier falsch liegen, aber es sieht so aus, als würde der Compiler denken, dass MESSAGE_ID verwendet wird, um sicherzustellen, dass MessageFactory: Register wird aufgerufen).

Dies scheint sehr unintelligent zu mir, so dass es ein Compiler Bug sein kann. (I testen dies in g ++ 4.3.2, ich bin neugierig zu wissen, wie Comeau C++, zum Beispiel behandelt sie.)

Explizit Instanziieren MESSAGE_ID genügt auch, zumindest in g ++ 4.3.2:

template const uint16_t PingMessage::MESSAGE_ID; 

Aber das ist noch mehr unnötige Arbeit als die Bereitstellung eines leeren Standardkonstruktors.

Ich kann nicht eine gute Lösung mit Ihrem aktuellen Ansatz denken; Ich wäre persönlich versucht, zu einer Technik zu wechseln (wie Makros oder mit einem Skript, um einen Teil Ihrer Quelldateien zu generieren), die weniger auf erweitertem C++ beruhte. (Ein Skript hätte den zusätzlichen Vorteil der Aufrechterhaltung der MESSAGE_IDs Lockerung.)

Als Antwort auf Ihre Kommentare:

Singletons sind allgemein vermieden werden, weil sie oft überstrapaziert als schlecht getarnte globale Variablen . Es gibt jedoch einige Male, wenn Sie wirklich eine globale Variable benötigen, und eine globale Registrierung der verfügbaren Nachrichtenunterklassen ist eine dieser Zeiten.

Ja, der Code, den Sie angegeben haben, initialisiert MESSAGE_ID, aber ich sprach von explicitly instantiating jede Instanz der Unterklasse von MESSAGE_ID. Explizite Instanziierung bezieht sich auf das Instruieren des Compilers, eine Vorlage zu instanziieren, selbst wenn er denkt, dass diese Vorlageninstanz nicht anderweitig verwendet wird.

Ich vermute, dass die statische Funktion mit der volatilen Zuweisung gibt es den Compiler zu Trick oder zwingen, die MESSAGE_ID Zuweisung zu generieren (um die Probleme zu umgehen, Dash-Tom-Bang und ich wies mit dem Compiler oder Linker ablegen oder nicht instanziieren die Zuordnung).

+0

Das OP sollte auch bedenken, dass viele Linker nicht referenzierte Objekte automatisch entfernen, auch wenn sie interessante Arbeit leisten (das heißt, die Initialisierung der statischen Mitglieder löst die Registrierung in einer Masterliste aus). Visual Studio zum Beispiel wird die statische Elementinitialisierung in Bibliotheken (also Code, der nicht Teil Ihres "ausführbaren" Projekts ist) gerne wegwerfen, wenn die ausführbare Datei nicht explizit auf dieses statische Objekt verweist. Sie können jeden einzelnen zu einer Liste von "implizit referenzierten Objekten" in VS hinzufügen, aber das ist noch ein mühsamer IMO. –

+0

Danke für deine Antworten. Ich habe viel von ihnen gelernt. Ich nehme keine Anerkennung von diesem Code, wie ich es aus einer japanischen Netzwerkbibliothek namens neun kopiert. Ich schaue jetzt in die Singleton Sache, aber ich verstehe, dass diese vermieden werden sollen. Ich habe die Vorlage, die die Konstruktoren registriert auch explizit die MESSAGE_ID? Sie können dort ein Gleichheitszeichen sehen. Schließlich hat die ursprüngliche Implementierung diesen Code in der Vorlage MessageTmpl: statische Void Enable() {flüchtige uint16_t x = MESSAGE_ID; } Aber ich habe keine Ahnung, wie dies verwendet wird, um die Registrierung des Konstruktors zu aktivieren. – Horacio

+1

Die statische Leere Enable() {flüchtig uint16_t x = MESSAGE_ID; } scheint zu verhindern, dass die Linker-Optimierung die Objekte entfernt. Da x flüchtig ist, kann die Zuweisung nicht optimiert werden. –

0

2: Sie einen dynamischen Container verwenden könnten, aber dann würden Sie auch ändern mußten, wie die Registrierung etc. Zum Beispiel können Sie eine Karte mit einem int als Schlüssel und einem Funktionszeiger als Element verwenden:

typedef Message* (*NewMessageFun)(); 

template< class tMessage > 
Message* NewMessage() 
{ 
    return new tMessage(); 
}; 

class PingMessage : public MessageImpl 
{ 
public: 
    enum{ _MESSAGE_ID = 10 }; 
}; 

class PongMessage 
{ 
public: 
    enum{ _MESSAGE_ID = 11 }; 
} 

//factory 
std::map< int, NewMessageFun > mymap; 
bool Register(const int type, NewMessageFun fun) 
{ 
    if(mymap.contains(type)) 
    return false; //already registered! 
    mymap[ type ] = fun; 
    return true; 
} 

template< class tMessage > 
bool RegisterAny() //shortcut 
{ 
    return Register(tMessage::_MESSAGE_ID, NewMessage<tMessage>); 
} 
// 

//main 
factory.RegisterAny<PingMessage>(); 
factory.RegisterAny<PongMessage>(); 
// 

Oder in Ihrem aktuellen Code, nur eine sinnvolle Zuordnung Größe verwenden und Laufzeitgrenzen haben die Überprüfung zu viele Registrierungen zu sehen sind. Und vielleicht eine "Unregister" -Methode liefern.

+0

Ja, ich habe versucht, eine Karte, aber es segfaulted ... aber dank Josh Kelley und Noah Roberts Antworten fand ich das Problem (statische Reihenfolge der Initialisierung). Ich denke, Singleton muss ich gehen, aber ich habe gehört, dass das böse ist. – Horacio

+1

Singletons sind nicht immer böse. Wenn Sie denken, für Ihren Fall ist ein Singleton der beste Ansatz, dann ist es. – stijn

2

Ich denke, dass Sie in unspezifizierten Verhalten laufen, weil Ihre Registrierungen auftreten können, bevor das Objekt, in das Sie sie stecken möchten. Sie erhalten möglicherweise gute Ergebnisse, da der Speicherplatz des Arrays in den Haupt-Stack des Programms integriert ist. Wer weiß ...

Der Fix für diesen, den ich verwendet habe, ist, um die Registrierungsfunktion entweder extern oder eine Elementfunktion statt statisch zu machen. Dann mit einem Meyers Singleton:


MessageFactory * MessageFactory::instance() 
{ 
    static MessageFactory fact; 
    return &fact; 
} 

Auf diese Weise Ihre Nachricht Fabrik wird durch irgendetwas anderes und wird garantiert auf Zugang erstellt werden, zur Verfügung stehen, wenn Sie versuchen, es zu benutzen (weil versuchen, es das erste Mal, es schafft zu verwenden).

0

hier Angebot mit Karte

#include <map> 
#include <iostream> 
#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <inttypes.h> 

//typedef Message *; 
class MessageFactory { 
public: 
    struct register_base { 
     virtual int id() const = 0; 
     virtual Message* new_() = 0; 
    }; 
    template<class C> 
    struct register_ : register_base { 
     static const int ID; 
     register_() : id_(ID) {} // force ID initialization 
     int id() const { return C::factory_key; } 
     Message* new_() { return new C(); } 
    private: 
     const int id_; 
    }; 
    static uint16_t Register(register_base* message) { 
     printf("Registering constructor for msg id %d\n", message->id()); 
     m_List[message->id()] = message; 
     return message->id(); 
    } 
    static Message *Create(uint16_t msgid) { 
     return m_List[msgid]->new_(); 
    } 
    static std::map<int,register_base*> m_List; 
}; 
std::map<int, MessageFactory::register_base*> MessageFactory::m_List; 

template<class C> 
const int MessageFactory::register_<C>::ID = 
    MessageFactory::Register(new MessageFactory::register_<C>()); 


class Message { 
public: 
    virtual ~Message() {} 
    int getMessageType() const { 
     return m_type; 
    } 
    virtual void say() = 0; 
protected: 
    uint16_t m_type; 
}; 

class PingMessage: public Message, private MessageFactory::register_<PingMessage> { 
public: 
    static const int factory_key = 10; 
    PingMessage() { } // must call explicitly to register 
    virtual void say() { 
     printf("Ping\n"); 
    } 
}; 

class PongMessage:public Message, private MessageFactory::register_<PongMessage> { 
public: 
    static const int factory_key = 11; 
    PongMessage() { } 
    void say() { 
     printf("Pong\n"); 
     std::cout << this->id() << std::endl; 
    } 
}; 



int main(int argc, char **argv) 
{ 
    Message *msg1; 
    Message *msg2; 

    msg1 = MessageFactory::Create(10); 
    msg1->say(); 

    msg2 = MessageFactory::Create(11); 
    msg2->say(); 

    delete msg1; 
} 
+0

Danke, ich habe dieses Problem behoben, indem ich die std :: map-Deklaration vor den MessageTmpl-Registrierungscode verschoben habe, wie von Josh Kelley erwähnt. Jetzt kann ich anstelle eines festen Arrays eine std :: map verwenden. Noch muss ich dies zu einem Singleton machen, um Probleme zu vermeiden, da die Nachrichtennummer zunimmt. – Horacio

3

Dies ist eine modifizierte Version, die eine MessageFactory Singleton verwendet und std :: map zum Speichern von Konstrukteuren leicht modifiziert. Es funktioniert bisher sehr gut, aber Kommentare sind willkommen.

Ich bin immer noch auf der Suche nach einer Möglichkeit, Erstellen von Konstruktoren für jede Nachrichtenklasse zu vermeiden. Ich weiß, dass es möglich ist, weil die ursprüngliche Bibliothek es tun kann. Leider habe ich nur die Header-Dateien, also keine Ahnung von den Implementierungsdetails.

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 
#include <inttypes.h> 
#include <map> 

class Message 
{ 
    protected: 
     Message() {}; 

    public: 
     virtual ~Message() { } 
     int getMessageType() const { return m_type; } 
     virtual void say() = 0; 

    protected: 
     uint16_t m_type; 
}; 

template<int TYPE, typename IMPL> 
class MessageTmpl: public Message 
{ 
    enum { _MESSAGE_ID = TYPE }; 
    public: 
    static Message* Create() { return new IMPL(); } 
    static const uint16_t MESSAGE_ID; // for registration 
    static void Enable() { volatile uint16_t x = MESSAGE_ID; } 
    protected: 
     MessageTmpl() { m_type = MESSAGE_ID; } //use parameter to instanciate template 
}; 

class MessageFactory 
{ 
    public: 
    typedef Message* (*t_pfFactory)(); 

    static MessageFactory *getInstance() 
    { 
     static MessageFactory fact; 
     return &fact; 
    } 

    uint16_t Register(uint16_t msgid, t_pfFactory factoryMethod) 
    { 
     printf("Registering constructor for msg id %d\n", msgid); 
     m_List[msgid] = factoryMethod; 
     return msgid; 
    } 

    Message *Create(uint16_t msgid) 
    { 
     return m_List[msgid](); 
    } 

    std::map<uint16_t, t_pfFactory> m_List; 

    private: 
    MessageFactory() {}; 
    MessageFactory(MessageFactory const&) {}; 
    MessageFactory& operator=(MessageFactory const&); 
    ~MessageFactory() {}; 
}; 

//std::map<uint16_t, t_pfFactory> MessageFactory::m_List; 

template <int TYPE, typename IMPL> 
const uint16_t MessageTmpl<TYPE, IMPL>::MESSAGE_ID = MessageFactory::getInstance()->Register(
    MessageTmpl<TYPE, IMPL >::_MESSAGE_ID, &MessageTmpl<TYPE, IMPL >::Create); 


class PingMessage: public MessageTmpl < 10, PingMessage > 
{ 
    public: 
    PingMessage() {} 
    virtual void say() { printf("Ping\n"); } 
}; 

class PongMessage: public MessageTmpl < 11, PongMessage > 
{ 
    public: 
    PongMessage() {} 
    virtual void say() { printf("Pong\n"); } 
}; 

int main(int argc, char **argv) 
{ 
    Message *msg1; 
    Message *msg2; 

    msg1 = MessageFactory::getInstance()->Create(10); 
    msg1->say(); 

    msg2 = MessageFactory::getInstance()->Create(11); 
    msg2->say(); 

    delete msg1; 
    delete msg2; 

    return 0; 
} 
0

Für alle Nachrichtenklassen, die Unterklassen von MessageTmpl sind ich den Konstruktor zu implementieren. Wenn nicht, wird es nicht in der MessageFactory registriert.

Ich habe mit dieser Idee experimentiert und einen Weg gefunden, die Instanziierung der Registrierungsvariablen zu erzwingen, ohne etwas in der abgeleiteten Klasse machen zu müssen. Erstellen Sie eine virtuelle Funktion in der Vorlage, die auf die Registrierungsvariable zugreift. Dies zwingt die Funktion, instanziiert zu werden, da die virtuelle Funktion dort sein muss, ob sie jemals aufgerufen wird oder nicht.

Hier ist mein Grund auf neu Code:

#include <boost/array.hpp> 
#include <iostream> 

struct thingy 
{ 
    virtual ~thingy() {} 
    virtual void print_msg() const = 0; 
    virtual size_t id() const = 0; 

    bool registered_already() const { return registered; } 
protected: 
    bool registered; 
}; 

struct holder 
{ 
    enum index { 
    ID_OPEN 
    , ID_SAVE 
    , ID_SAVEAS 
    , COUNT 
    }; 

    static holder& instance() 
    { 
    static holder inst; 
    return inst; 
    } 

    thingy& operator[] (size_t i) 
    { 
    assert(thingys[i] && "Not registered."); 
    return *thingys[i]; 
    } 

    bool registered(size_t i) const { return thingys[i] != 0; } 

    ~holder() { std::for_each(thingys.begin(), thingys.end(), [](thingy* t) { delete t; }); } 

    index reg(thingy* t, index i) 
    { 
    assert(!thingys[i] && "Thingy registered at this ID already"); 
    thingys[i] = t; 
    return i; 
    } 

private: 

    holder() : thingys() {} 

    boost::array< thingy*, COUNT > thingys; 
}; 

template < typename Derived, holder::index i > 
struct registered_thingy : thingy 
{ 
    size_t id() const { return registration; } 
private: 
    static holder::index registration; 
}; 

template < typename T, holder::index i > 
holder::index registered_thingy<T,i>::registration = holder::instance().reg(new T, i); 

struct thingy1 : registered_thingy<thingy1,holder::ID_OPEN> 
{ 
    void print_msg() const { std::cout << "thingy1\n"; } 
}; 

struct thingy2 : registered_thingy<thingy2, holder::ID_SAVE> 
{ 
    void print_msg() const { std::cout << "thingy2\n"; } 
}; 

struct thingy3 : registered_thingy<thingy3, holder::ID_SAVEAS> 
{ 
    void print_msg() const { std::cout << "thingy3\n"; } 
}; 



int main() 
{ 
    holder::instance()[holder::ID_OPEN].print_msg(); 

    std::cin.get(); 
} 
+0

Es tut mir leid zu sagen, dass ich diesen Code nicht funktionieren lassen kann. Ich bekomme die Nicht registrierte Behauptung, was bedeutet, dass die Dinge nicht registriert sind. Wenn ich die dingys-Konstruktoren zur Verfügung stelle, funktioniert der Code gut. – Horacio

+0

Welchen Compiler benutzen Sie? Es funktioniert für mich und soweit ich es sagen kann sollte es. Ich glaube, der Compiler ist gezwungen, id(), die die Variable ... –

+0

Damn verwendet. 14.7.1/9: "... Es ist nicht spezifiziert, ob eine Implementierung eine virtuelle Elementfunktion einer Klassenvorlage implizit instanziiert, wenn die virtuelle Elementfunktion andernfalls nicht instanziiert würde." –

1

konnte ich den Horacio Code arbeiten machen, ohne dass die Konstrukteure in den abgeleiteten Klassen zu verwenden. Ich habe die enable-Funktion innerhalb der say-Funktion der abgeleiteten Klassen aufgerufen.

class PingMessage: public MessageTmpl < 10, PingMessage > 
{ 
    public: 
    //PingMessage() {} 
    virtual void say() 
    { 
    enable(); // virtual (not static) function of the template class 
    printf ("Ping\n"); 
    } 
}; 
Verwandte Themen