Hor*_*cio 11 c++ templates factory-pattern
当实现MessageFactory类来实例化Message对象时,我使用了类似的东西:
class MessageFactory
{
public:
static Message *create(int type)
{
switch(type) {
case PING_MSG:
return new PingMessage();
case PONG_MSG:
return new PongMessage();
....
}
}
Run Code Online (Sandbox Code Playgroud)
这工作正常但每次添加新消息时我都要添加一个新的XXX_MSG并修改switch语句.
经过一些研究后,我发现了一种在编译时动态更新MessageFactory的方法,因此我可以添加任意数量的消息,而无需修改MessageFactory本身.这样可以更简洁,更容易维护代码,因为我不需要修改三个不同的位置来添加/删除消息类:
#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;
}
Run Code Online (Sandbox Code Playgroud)
这里的模板通过向MessageFactory类注册所有新的Message类(例如PingMessage和PongMessage)来实现神奇,它们是MessageTmpl的子类.
这很好用并简化了代码维护,但我仍然对这个技术有一些疑问:
这是一种已知的技术/模式吗?是什么名字?我想搜索更多关于它的信息.
我想创建用于存储新构造函数MessageFactory :: m_List [65536] std :: map 的数组,但这样做会导致程序在到达main()之前发生段错误.创建一个包含65536个元素的数组是过度的,但我还没有找到一种方法使它成为一个动态容器.
对于作为MessageTmpl子类的所有消息类,我必须实现构造函数.如果不是,它将不会在MessageFactory中注册.
例如,注释PongMessage的构造函数:
class PongMessage: public MessageTmpl < 11, PongMessage >
{
public:
//PongMessage() {} /* HERE */
virtual void say() { printf("Pong\n"); }
};
Run Code Online (Sandbox Code Playgroud)
会导致PongMessage类没有被MessageFactory注册,并且程序会在MessageFactory :: Create(11)行中出现段错误.问题是
为什么课程不会注册?必须添加我需要的100多条消息的空实现,感觉效率低下且不必要.
答案一
派生这样的类的一般技术是奇怪的重复模板模式(CRTP):
class PingMessage: public MessageTmpl < 10, PingMessage >
Run Code Online (Sandbox Code Playgroud)
使用模板类的静态成员初始化来注册该类的子类的特定技术(IMO)简直太棒了,我以前从未见过.单元测试框架(如UnitTest ++和Google Test)使用的一种更常见的方法是提供宏,它声明一个类和一个单独的静态变量来初始化该类.
答案二
静态变量按列出的顺序初始化.如果在MessageFactory :: Register调用之前移动m_List声明,则应该是安全的.还要记住,如果您开始在多个文件中声明Message子类,则必须将m_List包装为单例,并在每次使用之前检查它是否已初始化,因为C++静态初始化顺序为fiasco.
答案三
C++编译器只会实例化实际使用的模板成员.模板类的静态成员不是我用过的C++领域,所以我可能在这里错了,但看起来提供构造函数足以让编译器认为使用了MESSAGE_ID(从而确保了MessageFactory ::注册被称为).
这对我来说似乎非常不直观,所以它可能是编译器错误.(我在g ++ 4.3.2中对此进行了测试;我很想知道Comeau C++是如何处理它的.)
显式实例化MESSAGE_ID也足够了,至少在g ++ 4.3.2中:
template const uint16_t PingMessage::MESSAGE_ID;
Run Code Online (Sandbox Code Playgroud)
但这比提供一个空的默认构造函数更加不必要.
使用您当前的方法我想不出一个好的解决方案; 我个人很想转而使用一种技术(例如宏或使用脚本来生成部分源文件),这种技术较少依赖于高级C++.(脚本的另一个优点是可以简化MESSAGE_ID的维护.)
回应你的意见:
单身人士通常应该避免,因为他们经常被过度使用,因为他们伪装成全球变量.但是,有几次,当你确实需要一个全局变量时,可用的Message子类的全局注册表就是其中之一.
是的,您提供的代码是初始化MESSAGE_ID,但我在谈论显式实例化每个子类的MESSAGE_ID实例.显式实例化指的是指示编译器实例化模板,即使它认为不会以其他方式使用该模板实例.
我怀疑具有volatile赋值的静态函数可以欺骗或强制编译器生成MESSAGE_ID赋值(以解决dash-tom-bang和我指出的编译器或链接器丢弃或不实例化赋值的问题).
这是修改后的版本,它使用MessageFactory单例和std :: map来存储构造函数。到目前为止效果很好,但欢迎发表评论。
我仍在尝试寻找一种避免为每个消息类创建构造函数的方法。我知道这是可能的,因为原始库可以做到。不幸的是,我只有头文件,所以对实现细节一无所知。
#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;
}
Run Code Online (Sandbox Code Playgroud)