Registre dinámicamente los métodos de constructor en una AbstractFactory en tiempo de compilación utilizando plantillas de C ++
Al implementar una clase MessageFactory para instaurar objetos Message utilicé algo como:
class MessageFactory
{
public:
static Message *create(int type)
{
switch(type) {
case PING_MSG:
return new PingMessage();
case PONG_MSG:
return new PongMessage();
....
}
}
Esto funciona bien, pero cada vez que agrego un nuevo mensaje tengo que agregar un nuevo XXX_MSG y modificar la declaración de cambio.
Después de investigar un poco, encontré una manera de actualizar dinámicamente MessageFactory en tiempo de compilación para poder agregar tantos mensajes como quiera sin necesidad de modificar el propio MessageFactory. Esto permite un código más limpio y fácil de mantener, ya que no necesito modificar tres lugares diferentes para agregar / eliminar clases de mensajes:
#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;
}
La plantilla aquí hace la magia registrándose en la clase MessageFactory, todas las nuevas clases de mensajes (por ejemplo, PingMessage y PongMessage) que se subclasifican desde MessageTmpl.
Esto funciona muy bien y simplifica el mantenimiento del código, pero todavía tengo algunas preguntas sobre esta técnica:
¿Es esta una técnica / patrón conocido? ¿cual es el nombre? Quiero buscar más información al respecto.
Quiero hacer la matriz para almacenar nuevos constructores.MessageFactory :: m_List [65536] un std :: map pero al hacerlo causa que el programa se desconecte incluso antes de llegar a main (). Crear una matriz de 65536 elementos es excesivo, pero no he encontrado una manera de hacer de este un contenedor dinámico.
Para todas las clases de mensajes que son subclases de MessageTmpl, tengo que implementar el constructor. Si no, no se registrará en MessageFactory.
Por ejemplo comentando el constructor de PongMessage:
class PongMessage: public MessageTmpl < 11, PongMessage >
{
public:
//PongMessage() {} /* HERE */
virtual void say() { printf("Pong\n"); }
};
resultaría en que la clase PongMessage no sea registrada por MessageFactory y el programa se segfault enMessageFactory :: Crear (11) línea. La pregunta es
¿Por qué la clase no se registra? Tener que agregar la implementación vacía de los más de 100 mensajes que necesito se siente ineficiente e innecesario.