Dynamically register constructor methods in an AbstractFactory at compile time using C++ templates(在编译时使用 C++ 模板在 AbstractFactory 中动态注册构造函数方法)
问题描述
在实现 MessageFactory 类来实例化 Message 对象时,我使用了类似的东西:
When implementing a MessageFactory class to instatiate Message objects I used something like:
class MessageFactory
{
public:
static Message *create(int type)
{
switch(type) {
case PING_MSG:
return new PingMessage();
case PONG_MSG:
return new PongMessage();
....
}
}
这没问题,但每次添加新消息时,我都必须添加新的 XXX_MSG 并修改 switch 语句.
This works ok but every time I add a new message I have to add a new XXX_MSG and modify the switch statement.
经过一番研究,我找到了一种在编译时动态更新 MessageFactory 的方法,这样我就可以添加任意数量的消息,而无需修改 MessageFactory 本身.这使得代码更清晰、更易于维护,因为我不需要修改三个不同的地方来添加/删除消息类:
After some research I found a way to dynamically update the MessageFactory at compile time so I can add as many messages as I want without need to modify the MessageFactory itself. This allows for cleaner and easier to maintain code as I do not need to modify three different places to add/remove message classes:
#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
", 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
"); }
};
class PongMessage: public MessageTmpl < 11, PongMessage >
{⋅
public:
PongMessage() {}
virtual void say() { printf("Pong
"); }
};
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;
}
这里的模板通过注册到 MessageFactory 类来实现魔法,所有新的 Message 类(例如 PingMessage 和 PongMessage)都是 MessageTmpl 的子类.
The template here does the magic by registering into the MessageFactory class, all new Message classes (e.g. PingMessage and PongMessage) that subclass from MessageTmpl.
这很好用并简化了代码维护,但我仍然对这项技术有一些疑问:
This works great and simplifies code maintenance but I still have some questions about this technique:
这是一种已知的技术/模式吗?是什么名字?我想搜索更多信息关于它.
Is this a known technique/pattern? what is the name? I want to search more info about it.
我想创建用于存储新构造函数的数组 MessageFactory::m_List[65536]一个 std::map 但这样做会导致程序在到达 main() 之前发生段错误.创建一个由 65536 个元素组成的数组是矫枉过正的,但我还没有找到一种方法使其成为动态容器.
I want to make the array for storing new constructors MessageFactory::m_List[65536] a std::map but doing so causes the program to segfault even before reaching main(). Creating an array of 65536 elements is overkill but I have not found a way to make this a dynamic container.
对于作为 MessageTmpl 子类的所有消息类,我必须实现构造函数.如果不是,则不会在 MessageFactory 中注册.
For all message classes that are subclasses of MessageTmpl I have to implement the constructor. If not it won't register in the MessageFactory.
例如注释 PongMessage 的构造函数:
For example commenting the constructor of the PongMessage:
class PongMessage: public MessageTmpl < 11, PongMessage >
{
public:
//PongMessage() {} /* HERE */
virtual void say() { printf("Pong
"); }
};
会导致 PongMessage 类没有被 MessageFactory 注册,并且程序会在 MessageFactory::Create(11) 行中出现段错误.问题是
为什么班级不会注册?必须添加 100+ 的空实现我需要的消息感觉效率低下和不必要.
would result in the PongMessage class not being registered by the MessageFactory and the
program would segfault in the MessageFactory::Create(11) line. The question is
why the class won't register? Having to add the empty implementation of the 100+
messages I need feels inefficient and unnecessary.
推荐答案
答案一
派生这样的类的一般技术是Curiously Recurring Template Pattern (CRTP):
The general technique of deriving a class like this is the Curiously Recurring Template Pattern (CRTP):
class PingMessage: public MessageTmpl < 10, PingMessage >
您使用模板类的静态成员初始化来注册该类的子类的具体技术 (IMO) 简直太棒了,我以前从未见过.一种更常见的方法,由单元测试框架使用,例如 UnitTest++ 和 Google Test 是提供宏来声明类和初始化该类的单独静态变量.
Your specific technique of using a template class's static member initialization to register subclasses of that class is (IMO) simply brilliant, and I've never seen that before. A more common approach, used by unit test frameworks like UnitTest++ and Google Test, is to provide macros that declare both a class and a separate static variable initializing that class.
回答二
静态变量按列出的顺序初始化.如果在 MessageFactory::Register 调用之前移动 m_List 声明,则应该是安全的.还请记住,如果您开始在多个文件中声明 Message 子类,则必须将 m_List 包装为单例并在每次使用前检查它是否已初始化,因为 C++ 静态初始化顺序失败.
Static variables are initialized in the order listed. If you move your m_List declaration before your MessageFactory::Register calls, you should be safe. Also keep in mind that if you start declaring Message subclasses in more than one file, you'll have to wrap m_List as a singleton and check that it's initialized before each use, due to the C++ static initialization order fiasco.
答案三
C++ 编译器只会实例化实际使用的模板成员.模板类的静态成员不是我经常使用的 C++ 领域,所以我在这里可能是错的,但看起来提供构造函数足以让编译器认为使用 MESSAGE_ID(从而确保 MessageFactory::寄存器被调用).
C++ compilers will only instantiate template members that are actually used. Static members of template classes is not an area of C++ that I've used much, so I could be wrong here, but it looks like providing the constructor is enough to make the compiler think that MESSAGE_ID is used (thus ensuring that MessageFactory::Register is called).
这对我来说似乎很不直观,所以它可能是一个编译器错误.(我在 g++ 4.3.2 中对此进行了测试;例如,我很想知道 Comeau C++ 是如何处理它的.)
This seems very unintuitive to me, so it may be a compiler bug. (I was testing this in g++ 4.3.2; I'm curious to know how Comeau C++, for example, handles it.)
显式实例化 MESSAGE_ID 也足够了,至少在 g++ 4.3.2 中:
Explicitly instantiating MESSAGE_ID also suffices, at least in g++ 4.3.2:
template const uint16_t PingMessage::MESSAGE_ID;
但这比提供一个空的默认构造函数更没有必要.
But that's even more unnecessary work than providing an empty default constructor.
使用您当前的方法,我想不出一个好的解决方案;我个人很想切换到较少依赖高级 C++ 的技术(例如宏或使用脚本生成部分源文件).(脚本具有简化 MESSAGE_ID 维护的额外优势.)
I can't think of a good solution using your current approach; I'd personally be tempted to switch to a technique (such as macros or using a script to generate part of your source files) that relied less on advanced C++. (A script would have the added advantage of easing maintenance of MESSAGE_IDs.)
回应您的评论:
单例通常应该避免,因为它们经常被过度使用为伪装不当的全局变量.然而,有时您确实需要一个全局变量,可用 Message 子类的全局注册表就是其中之一.
Singletons are generally to be avoided because they're often overused as poorly disguised global variables. There are a few times, however, when you really do need a global variable, and a global registry of available Message subclasses is one of those times.
是的,您提供的代码正在初始化 MESSAGE_ID,但我说的是 显式实例化每个子类的 MESSAGE_ID 实例.显式实例化是指指示编译器实例化模板,即使它认为该模板实例不会被使用.
Yes, the code that you provided is initializing MESSAGE_ID, but I was talking about explicitly instantiating each subclass's instance of MESSAGE_ID. Explicit instantiation refers to instructing the compiler to instantiate a template even if it thinks that that template instance won't otherwise be used.
我怀疑带有 volatile 赋值的静态函数是为了欺骗或强制编译器生成 MESSAGE_ID 赋值(以解决 dash-tom-bang 和我在编译器或链接器是否删除时指出的问题)实例化赋值).
I suspect that the static function with the volatile assignment is there to trick or force the compiler into generating the MESSAGE_ID assignment (to get around the problems that dash-tom-bang and I pointed out with the compiler or linker dropping or not instantiating the assignment).
这篇关于在编译时使用 C++ 模板在 AbstractFactory 中动态注册构造函数方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持编程学习网!