在编译时使用 C++ 模板在 AbstractFactory 中动态注册构造函数方法 [英] Dynamically register constructor methods in an AbstractFactory at compile time using C++ templates

查看:31
本文介绍了在编译时使用 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:

  1. 这是一种已知的技术/模式吗?是什么名字?我想搜索更多信息关于它.

  1. 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 中动态注册构造函数方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆