使用C++模板在编译时在AbstractFactory中动态注册构造函数方法

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的子类.

这很好用并简化了代码维护,但我仍然对这个技术有一些疑问:

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

  2. 我想创建用于存储新构造函数MessageFactory :: m_List [65536] std :: map 的数组,但这样做会导致程序在到达main()之前发生段错误.创建一个包含65536个元素的数组是过度的,但我还没有找到一种方法使它成为一个动态容器.

  3. 对于作为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多条消息的空实现,感觉效率低下且不必要.

Jos*_*ley 7

答案一

派生这样的类的一般技术是奇怪的重复模板模式(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和我指出的编译器或链接器丢弃或不实例化赋值的问题).


Hor*_*cio 5

这是修改后的版本,它使用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)