本文介绍了使用C ++模板在编译时在AbstractFactory中动态地注册构造方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当实现一个MessageFactory类来实现Message对象时,我使用了类似于:

  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语句。



经过一些研究,我发现了一种方法来动态更新MessageFactory在编译时间,所以我可以添加尽可能多的消息,我想要而不需要修改MessageFactory本身。这允许更清洁和更容易维护代码,因为我不需要修改三个不同的地方添加/删除消息类:

  #include< stdio.h> 
#include< stdlib.h>
#include< string.h>
#include< inttypes.h>

类消息
{
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消息*(* t_pfFactory)();
classMessageFactory⋅
{
public:
static uint16_t注册(uint16_t msgid,t_pfFactory factoryMethod)
{
printf(Registering constructor for msg id% d\\\
,msgid);
m_List [msgid] = factoryMethod;
return msgid;
}

静态消息* 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& ;

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;
消息* msg2;

msg1 = MessageFactory :: Create(10);
msg1-> say();

msg2 = MessageFactory :: Create(11);
msg2-> say();

delete msg1;
delete msg2;

return 0;
}

这里的模板通过注册到MessageFactory类,从MessageTmpl子类的类(例如PingMessage和PongMessage)。



这很好,简化了代码维护,但我仍然有一些关于这种技术的问题:


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


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


  3. 作为MessageTmpl的子类的消息类我必须实现
    的构造函数。



    例如,注释PongMessage的构造函数:

      class PongMessage:public MessageTmpl< 11,PongMessage> 
    {
    public:
    // PongMessage(){} / * HERE * /
    virtual void say(){printf(Pong \\\
    ); }
    };

    会导致PongMessage类未由MessageFactory注册,而
    程序会将segfault在 MessageFactory :: Create(11)行中。问题是

    为什么类不会注册?不得不添加100+
    消息的空实现,我需要感觉低效和不必要。



解决方案

回答一个

导出类似这样的类的一般技术是和,是提供声明类和单独的静态变量初始化该类的宏。



回答两个



静态变量按列出的顺序初始化。如果你在MessageFactory :: Register调用之前移动m_List声明,你应该是安全的。还要记住,如果你开始在多个文件中声明Message子类,你必须将m_List作为一个单例,并检查它在每次使用前初始化,因为。



回答三



C ++编译器只会实例化实际使用的模板成员。模板类的静态成员不是我使用过的C ++的一个领域,所以我可能在这里错了,但它看起来像提供构造函数足以使编译器认为MESSAGE_ID被使用(从而确保MessageFactory ::寄存器被调用)。



这似乎对我来说是不直观的,所以它可能是一个编译器错误。 (我在g ++ 4.3.2测试这个;我很想知道Comeau C ++如何处理它。)



显式实例化MESSAGE_ID也足够了,在最少在g ++ 4.3.2中:

 模板const uint16_t PingMessage :: MESSAGE_ID; 

但是这比提供一个空的默认构造函数更不必要的工作。



我不能想象一个好的解决方案使用您当前的方法;我个人倾向于切换到一种技术(例如宏或使用脚本生成部分源文件),而这些技术依赖于高级C ++。 (



回应您的意见



单例通常要避免,因为它们常常被过度使用,作为伪装的全局变量。然而,有几次,当你确实需要一个全局变量,并且可用的Message子类的全局注册表是这些时间之一。



是的,代码您提供的是初始化MESSAGE_ID,但我在谈论每个子类的MESSAGE_ID实例。显式实例化指的是指示编译器实例化模板,即使它认为该模板实例将不会被使用。



我怀疑带有volatile的静态函数赋值是有可能欺骗或强制编译器生成MESSAGE_ID赋值(为了解决dash-tom-bang的问题,我指出编译器或链接器删除或不实例化赋值)。


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();
         ....
    }
}

This works ok but every time I add a new message I have to add a new XXX_MSG and modify the switch statement.

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\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;                                                                                                                                                                                  
} 

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. Is this a known technique/pattern? what is the name? I want to search more infoabout it.

  2. 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.

  3. For all message classes that are subclasses of MessageTmpl I have to implementthe constructor. If not it won't register in the MessageFactory.

    For example commenting the constructor of the PongMessage:

     class PongMessage: public MessageTmpl < 11, PongMessage >       
     {                                                                                                                                                                                           
       public:                                                                                                                                                                                    
        //PongMessage() {} /* HERE */                                                                                                                                                                          
        virtual void say() { printf("Pong\n"); }                   
     };
    

    would result in the PongMessage class not being registered by the MessageFactory and theprogram 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.

解决方案

Answer One

The general technique of deriving a class like this is the Curiously Recurring Template Pattern (CRTP):

class PingMessage: public MessageTmpl < 10, PingMessage > 

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.

Answer Two

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.

Answer Three

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).

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.)

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.

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.)

In response to your comments:

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.

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.

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

10-30 06:22