13

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自体を変更せずに必要な数のメッセージを追加できるようにする方法を見つけました。これにより、メッセージクラスを追加/削除するために3つの異なる場所を変更する必要がないため、コードをよりクリーンで簡単に保守できます。

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

ここでのテンプレートは、MessageFactoryクラス、MessageTmplからサブクラス化されたすべての新しいMessageクラス(PingMessageやPongMessageなど)に登録することで魔法をかけます。

これはうまく機能し、コードのメンテナンスを簡素化しますが、この手法についてはまだいくつか質問があります。

  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"); }                   
     };
    

    その結果、PongMessageクラスはMessageFactoryによって登録されず、プログラムはMessageFactory :: Create(11)行でセグメンテーション違反を起こします。問題は
    、なぜクラスが登録されないのかということです。必要な100以上のメッセージの空の実装を追加する必要があると、非効率的で不必要に感じます。

4

7 に答える 7

9

回答1

このようなクラスを導出する一般的な手法は、Curiously Recurring Template Pattern(CRTP)です。

class PingMessage: public MessageTmpl < 10, PingMessage > 

テンプレートクラスの静的メンバーの初期化を使用してそのクラスのサブクラスを登録する特定の手法は、(IMO)単純に優れており、これまで見たことがありません。UnitTest++やGoogleTestなどの単体テストフレームワークで使用されるより一般的なアプローチは、クラスと、そのクラスを初期化する個別の静的変数の両方を宣言するマクロを提供することです。

回答2

静的変数は、リストされている順序で初期化されます。MessageFactory :: Register呼び出しの前にm_List宣言を移動する場合は、安全である必要があります。また、複数のファイルでメッセージサブクラスの宣言を開始する場合は、 C ++の静的初期化順序の失敗により、m_Listをシングルトンとしてラップし、使用する前に初期化されていることを確認する必要があることにも注意してください。

回答3

C ++コンパイラは、実際に使用されるテンプレートメンバーのみをインスタンス化します。テンプレートクラスの静的メンバーは、私がよく使用するC ++の領域ではないため、ここでは間違っている可能性がありますが、コンストラクターを提供するだけで、コンパイラーにMESSAGE_IDが使用されていると見なさせることができます(したがって、MessageFactory ::レジスタが呼び出されます)。

これは私には非常に直感的ではないように思われるので、コンパイラのバグである可能性があります。(私はこれをg ++ 4.3.2でテストしていました。たとえば、Comeau C ++がどのように処理するのか知りたいです。)

少なくともg++4.3.2では、MESSAGE_IDを明示的にインスタンス化することでも十分です。

template const uint16_t PingMessage::MESSAGE_ID;

しかし、それは空のデフォルトコンストラクターを提供するよりもさらに不必要な作業です。

私はあなたの現在のアプローチを使用して良い解決策を考えることができません。個人的には、高度なC ++にあまり依存しない手法(マクロやスクリプトを使用してソースファイルの一部を生成するなど)に切り替えたいと思います。(スクリプトには、MESSAGE_IDの保守を容易にするという追加の利点があります。)

あなたのコメントに応えて:

シングルトンは、偽装が不十分なグローバル変数として頻繁に使用されるため、通常は避ける必要があります。ただし、実際にグローバル変数が必要な場合がいくつかあり、使用可能なメッセージサブクラスのグローバルレジストリはその1つです。

はい、提供したコードはMESSAGE_IDを初期化していますが、私は各サブクラスのMESSAGE_IDのインスタンスを明示的にインスタンス化することについて話していました。明示的なインスタンス化とは、テンプレートインスタンスが他の方法では使用されないと考えられる場合でも、テンプレートをインスタンス化するようにコンパイラーに指示することを指します。

揮発性の割り当てを持つ静的関数は、コンパイラーにMESSAGE_ID割り当てを生成させるために存在するのではないかと思います(dash-tom-bangの問題を回避するために、コンパイラーまたはリンカーが割り当てをドロップまたはインスタンス化しないことを指摘しました)。

于 2010-05-17T16:29:24.140 に答える
6

これは、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;
}
于 2010-05-18T06:38:53.437 に答える
2

登録は、貼り付けたいオブジェクトが実行する前に発生する可能性があるため、不特定の動作に遭遇していると思います。配列のスペースがプログラムのメインスタックに組み込まれているため、問題のない結果が得られる可能性があります。知るか...

私が使用したこれに対する修正は、登録関数を静的ではなく外部関数またはメンバー関数にすることです。次に、マイヤーズシングルトンを使用します。


MessageFactory * MessageFactory::instance()
{
  static MessageFactory fact;
  return &fact;
}

このようにして、メッセージファクトリは他のユーザーがアクセスすると作成され、使用しようとしたときに使用可能であることが保証されます(初めて使用しようとすると作成されるため)。

于 2010-05-17T16:37:47.923 に答える
1

派生クラスのコンストラクターを使用せずに、Horacioコードを機能させることができました。派生クラスのsay関数内でenable関数を呼び出しました。

class PingMessage: public MessageTmpl < 10, PingMessage >
{ 
  public:
  //PingMessage () {}
  virtual void say ()
  {
    enable ();  // virtual (not static) function of the template class
    printf ("Ping\n");
  }
};
于 2011-10-23T23:25:06.433 に答える
0

MessageTmplのサブクラスであるすべてのメッセージクラスに対して、コンストラクターを実装する必要があります。そうでない場合は、MessageFactoryに登録されません。

私はこのアイデアを試していて、派生クラスで何も作成せずに登録変数のインスタンス化を強制する方法を思いつきました。登録変数にアクセスするテンプレートで仮想関数を作成します。これにより、関数が呼び出されるかどうかに関係なく、仮想関数が存在する必要があるため、関数が強制的にインスタンス化されます。

これが私のスクラッチコードです:

#include <boost/array.hpp>
#include <iostream>

struct thingy
{
  virtual ~thingy() {}
  virtual void print_msg() const = 0;
  virtual size_t id() const = 0;

  bool registered_already() const { return registered; }
protected:
  bool registered;
};

struct holder
{
  enum index {
    ID_OPEN
  , ID_SAVE
  , ID_SAVEAS
  , COUNT
  };

  static holder& instance()
  {
    static holder inst;
    return inst;
  }

  thingy& operator[] (size_t i)
  {
    assert(thingys[i] && "Not registered.");
    return *thingys[i];
  }

  bool registered(size_t i) const { return thingys[i] != 0; }

  ~holder() { std::for_each(thingys.begin(), thingys.end(), [](thingy* t) { delete t; }); }

  index reg(thingy* t, index i)
  {
    assert( !thingys[i] && "Thingy registered at this ID already" );
    thingys[i] = t;
    return i;
  }

private:

  holder() : thingys() {}

  boost::array< thingy*, COUNT > thingys;
};

template < typename Derived, holder::index i >
struct registered_thingy : thingy
{
  size_t id() const { return registration; }
private:
  static holder::index registration;
};

template < typename T, holder::index i >
holder::index registered_thingy<T,i>::registration = holder::instance().reg(new T, i);

struct thingy1 : registered_thingy<thingy1,holder::ID_OPEN>
{
  void print_msg() const { std::cout << "thingy1\n"; }
};

struct thingy2 : registered_thingy<thingy2, holder::ID_SAVE>
{
  void print_msg() const { std::cout << "thingy2\n"; }
};

struct thingy3 : registered_thingy<thingy3, holder::ID_SAVEAS>
{
  void print_msg() const { std::cout << "thingy3\n"; }
};



int main()
{
  holder::instance()[holder::ID_OPEN].print_msg();

  std::cin.get();
}
于 2010-06-30T17:53:54.093 に答える
0

2:動的コンテナを使用できますが、登録方法なども変更する必要があります。たとえば、キーとしてintを使用し、要素として関数ポインタを使用するマップを使用できます。

typedef Message* ( *NewMessageFun )();

template< class tMessage >
Message* NewMessage()
{
  return new tMessage();
};

class PingMessage : public MessageImpl
{
public:
  enum{ _MESSAGE_ID = 10 };
};

class PongMessage
{
public:
  enum{ _MESSAGE_ID = 11 };
}

//factory
std::map< int, NewMessageFun > mymap;
bool Register( const int type, NewMessageFun fun )
{
  if( mymap.contains( type ) )
    return false; //already registered!
  mymap[ type ] = fun;
  return true;
}

template< class tMessage >
bool RegisterAny() //shortcut
{
  return Register( tMessage::_MESSAGE_ID, NewMessage< tMessage > );
}
//

//main
factory.RegisterAny< PingMessage >();
factory.RegisterAny< PongMessage >();
//

または、現在のコードでは、適切な割り当てサイズを使用し、実行時の境界をチェックして、登録が多すぎることを確認します。そして多分「登録解除」メソッドを提供します。

于 2010-05-17T15:56:54.090 に答える
0

ここに地図を使用してわずかに変更されたリストがあります

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

//typedef Message *;
class MessageFactory {
 public:
    struct register_base {
        virtual int id() const = 0;
        virtual Message* new_() = 0;
    };
    template<class C>
    struct register_ : register_base {
        static const int ID;
        register_() : id_(ID)  {} // force ID initialization
        int id() const { return C::factory_key; }
        Message* new_() { return new C(); }
    private:
        const int id_;
    };
    static uint16_t Register(register_base* message) {
        printf("Registering constructor for msg id %d\n", message->id());
        m_List[message->id()] = message;
        return message->id();
    }
    static Message *Create(uint16_t msgid) {
        return m_List[msgid]->new_();
    }
    static std::map<int,register_base*> m_List;
};
std::map<int, MessageFactory::register_base*> MessageFactory::m_List;

template<class C>
const int MessageFactory::register_<C>::ID =
    MessageFactory::Register(new MessageFactory::register_<C>());


class Message {
public:
    virtual ~Message() {}
    int getMessageType() const {
        return m_type;
    }
    virtual void say() = 0;
protected:
    uint16_t m_type;
};

class PingMessage: public Message, private MessageFactory::register_<PingMessage> {
public:
    static const int factory_key = 10;
    PingMessage() { } // must call explicitly to register
    virtual void say() {
        printf("Ping\n");
    }
};

class PongMessage:public Message, private MessageFactory::register_<PongMessage> {
public:
    static const int factory_key = 11;
    PongMessage() { }
    void say() {
        printf("Pong\n");
        std::cout   << this->id() << std::endl;
    }
};



int main(int argc, char **argv)
{
    Message *msg1;
    Message *msg2;

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

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

    delete msg1;
}
于 2010-05-17T17:54:32.983 に答える