2009-02-24 26 views
110

私はファイルがあります:Base.hクラス名を保持する文字列からオブジェクトをインスタンス化する方法はありますか?

class Base; 
class DerivedA : public Base; 
class DerivedB : public Base; 

/*etc...*/ 

と別のファイル:BaseFactory.h

#include "Base.h" 

class BaseFactory 
{ 
public: 
    BaseFactory(const string &sClassName){msClassName = sClassName;}; 

    Base * Create() 
    { 
    if(msClassName == "DerivedA") 
    { 
     return new DerivedA(); 
    } 
    else if(msClassName == "DerivedB") 
    { 
     return new DerivedB(); 
    } 
    else if(/*etc...*/) 
    { 
     /*etc...*/ 
    } 
    }; 
private: 
    string msClassName; 
}; 

/*etc.*/ 

何とかこの文字列を実際の型(クラス)に変換する方法がありますので、BaseFactory可能なすべての派生クラスを知る必要はなく、if()をそれぞれ持っているでしょうか?この文字列からクラスを作成できますか?

これはC#でReflectionを使って行うことができると思います。 C++に似たものがありますか?

+0

C++ 0xでその部分の可能な命名さ想定しているとバリデーショナルテンプレート.. – smerlin

答えて

188

は、いや、何もありません、あなたがマッピング自分で行う場合を除きます。 C++には、実行時に型が決定されるオブジェクトを作成するメカニズムはありません。

template<typename T> Base * createInstance() { return new T; } 

typedef std::map<std::string, Base*(*)()> map_type; 

map_type map; 
map["DerivedA"] = &createInstance<DerivedA>; 
map["DerivedB"] = &createInstance<DerivedB>; 

をそして、あなたは新しいインスタンスを取得する

return map[some_string](); 

行うことができます:あなたは、しかし、自分自身をマッピングすることを行うにはマップを使用することができます。もう一つのアイデアは、型がその人自身を登録することです。

// in base.hpp: 
template<typename T> Base * createT() { return new T; } 

struct BaseFactory { 
    typedef std::map<std::string, Base*(*)()> map_type; 

    static Base * createInstance(std::string const& s) { 
     map_type::iterator it = getMap()->find(s); 
     if(it == getMap()->end()) 
      return 0; 
     return it->second(); 
    } 

protected: 
    static map_type * getMap() { 
     // never delete'ed. (exist until program termination) 
     // because we can't guarantee correct destruction order 
     if(!map) { map = new map_type; } 
     return map; 
    } 

private: 
    static map_type * map; 
}; 

template<typename T> 
struct DerivedRegister : BaseFactory { 
    DerivedRegister(std::string const& s) { 
     getMap()->insert(std::make_pair(s, &createT<T>)); 
    } 
}; 

// in derivedb.hpp 
class DerivedB { 
    ...; 
private: 
    static DerivedRegister<DerivedB> reg; 
}; 

// in derivedb.cpp: 
DerivedRegister<DerivedB> DerivedB::reg("DerivedB"); 

あなたが登録

#define REGISTER_DEC_TYPE(NAME) \ 
    static DerivedRegister<NAME> reg 

#define REGISTER_DEF_TYPE(NAME) \ 
    DerivedRegister<NAME> NAME::reg(#NAME) 

私も、これらの2のためのより良い名前があると確信しているためにマクロを作成することを決定することもできます。ここで使用するのにおそらく役立つ別のものはshared_ptrです。

共通の基底クラスを持たない無関係な型のセットがある場合は、代わりにboost::variant<A, B, C, D, ...>の戻り値の型を関数ポインタに渡すことができます。あなたはFooクラス、バーやバズを持っている場合と同様に、それは次のようになります。

typedef boost::variant<Foo, Bar, Baz> variant_type; 
template<typename T> variant_type createInstance() { 
    return variant_type(T()); 
} 

typedef std::map<std::string, variant_type (*)()> map_type; 

boost::variant組合のようなものです。初期化や割り当てにどのオブジェクトが使用されているかを調べることによって、どのタイプが格納されているかを知ることができます。そのドキュメントhereを見てください。最後に、生の関数ポインタの使用もやや古いです。現代のC++コードは、特定の関数/型から分離する必要があります。より良い方法を探すには、Boost.Functionを調べるとよいでしょう。これは、このように(マップ)になります。

typedef std::map<std::string, boost::function<variant_type()> > map_type; 

std::functionstd::shared_ptr含め、あまりにもC++の次のバージョンで利用できるようになります。

+3

派生クラスが自分自身を登録するという考えが大好きです。それはまさに私が探していたもので、派生したクラスが工場から存在するハードコーディングされた知識を取り除く方法です。 –

+1

元々somedaveが別の質問で投稿したこのコードは、make_pairのためにあいまいなテンプレートエラーでVS2010で失敗します。修正するには、make_pairをstd :: pair に変更し、それらのエラーを修正する必要があります。 BaseFactory :: map_type * BaseFactory :: map = new map_type();を追加することで修正されたいくつかのリンクエラーもあります。 to base.cpp –

+0

私はVs2010を使用し、make_pairの代わりにstd :: pairを使用してこれを実装する際に問題があります。コンパイルするとリンクエラーが発生します。静的DerivedRegisterに対応するものを追加する reg、static MouseFeatureRegister mouse_reg;うまくコンパイルします。しかし、MouseFeatureRegisterを追加すると、CvMaskOverlay :: mouse_reg( "Masking - Polygon");私はいくつかのリンクエラーを取得します。この考え方は、サブクラスが増加するカウンタを割り当てられたマップに文字列を登録するためだけです。テンプレートを使用しないでください –

6

なしありません。私のこの問題に対する解決策は、名前を作成メソッドにマップする辞書を作成することです。このように作成したいクラスは、作成メソッドを辞書に登録します。これは、GoF patterns bookで詳細に説明されています。

+3

誰でも、本を指すのではなく、これがどのパターンであるかを気にかけてください? – josaphatv

+0

彼はレジストリパターンに言及していると思います。 – jiggunjer

+1

この回答を読んでいる人には、辞書を使ってどのクラスをインスタンス化するかを決定する実装であるFactoryパターンの使用を参照していると思います。 – Grimeh

0

これは工場出荷時のパターンです。 wikipediaを参照してください(例:this)。あなたは、ひどいハックをせずに、文字列からタイプ自体を作成することはできません。なぜこれが必要ですか?

+0

私はファイルから文字列を読み込むため、これが必要です。これがあれば、正しいインスタンスを作成するために何も知る必要がないように汎用的な工場を持つことができます。これは非常に強力です。 –

+0

バスと車は両方とも車であるため、異なるクラス定義は必要ないと言っていますか?しかし、そうした場合、別の行を追加することは本当に問題ではありません:)マップのアプローチは同じ問題を抱えています。マップの内容を更新します。マクロのthingyは簡単なクラスで動作します。 – dirkgently

+0

私はバスや車を私の場合に作成するために、私は異なる定義を必要としない、そうでなければ工場設計パターンは決して使用されないと言っている。私の目標は工場をできるだけ愚かにすることでした。しかし、ここにはエスケープがないことがわかります:-) –

4

私はC++ファクトリについて別の質問に答えました。フレキシブルな工場が関心のある方はthereをご覧ください。私はET ++から古い方法を説明して、私にとって素晴らしいマクロを使用しようとしています。

ET++は、古いMacAppをC++とX11に移植するプロジェクトでした。その努力でエリックガンマなどが考え始めたデザインパターン

0

Tor Brede Vekterliは、求める機能を正確に提供するブースト拡張機能を提供します。現在のところ、現在のboostライブラリではやや厄介ですが、ベースネームスペースを変更して1.48_0で動作させることができました。私は、UIとエンジンとの間の相互作用のためにそれを使用する - - (反射など)そのようなことは、C++のために有用であろう理由を問う人たちへの答えで

http://arcticinteractive.com/static/boost/libs/factory/doc/html/factory/factory.html#factory.factory.reference

ユーザーがUIでオプションを選択し、エンジンはUI選択文字列を受け取り、所望のタイプのオブジェクトを生成する。

ここでフレームワークを使用することの最大の利点は、登録機能が各クラスの定義にあり(登録クラスごとに登録関数を呼び出すコードが1行だけで済むことです)フルーツリストを含むファイルに追加する必要があります。フルリストは、新しいクラスが派生するたびに手動で追加する必要があります。

私は工場を基本クラスの静的メンバーにしました。

2

ブースト::機能は非常に柔軟性のある工場出荷時のテンプレートがあります。http://www.boost.org/doc/libs/1_54_0/libs/functional/factory/doc/html/index.html

私の好みはしかし、マッピングおよびオブジェクト作成メカニズムを隠すラッパークラスを生成することです。私が遭遇する一般的なシナリオでは、いくつかの基本クラスの異なる派生クラスをキーにマップする必要があります。派生クラスにはすべて共通のコンストラクタ署名があります。ここに私がこれまで考え出した解決策があります。

#ifndef GENERIC_FACTORY_HPP_INCLUDED 

//BOOST_PP_IS_ITERATING is defined when we are iterating over this header file. 
#ifndef BOOST_PP_IS_ITERATING 

    //Included headers. 
    #include <unordered_map> 
    #include <functional> 
    #include <boost/preprocessor/iteration/iterate.hpp> 
    #include <boost/preprocessor/repetition.hpp> 

    //The GENERIC_FACTORY_MAX_ARITY directive controls the number of factory classes which will be generated. 
    #ifndef GENERIC_FACTORY_MAX_ARITY 
     #define GENERIC_FACTORY_MAX_ARITY 10 
    #endif 

    //This macro magic generates GENERIC_FACTORY_MAX_ARITY + 1 versions of the GenericFactory class. 
    //Each class generated will have a suffix of the number of parameters taken by the derived type constructors. 
    #define BOOST_PP_FILENAME_1 "GenericFactory.hpp" 
    #define BOOST_PP_ITERATION_LIMITS (0,GENERIC_FACTORY_MAX_ARITY) 
    #include BOOST_PP_ITERATE() 

    #define GENERIC_FACTORY_HPP_INCLUDED 

#else 

    #define N BOOST_PP_ITERATION() //This is the Nth iteration of the header file. 
    #define GENERIC_FACTORY_APPEND_PLACEHOLDER(z, current, last) BOOST_PP_COMMA() BOOST_PP_CAT(std::placeholders::_, BOOST_PP_ADD(current, 1)) 

    //This is the class which we are generating multiple times 
    template <class KeyType, class BasePointerType BOOST_PP_ENUM_TRAILING_PARAMS(N, typename T)> 
    class BOOST_PP_CAT(GenericFactory_, N) 
    { 
     public: 
      typedef BasePointerType result_type; 

     public: 
      virtual ~BOOST_PP_CAT(GenericFactory_, N)() {} 

      //Registers a derived type against a particular key. 
      template <class DerivedType> 
      void Register(const KeyType& key) 
      { 
       m_creatorMap[key] = std::bind(&BOOST_PP_CAT(GenericFactory_, N)::CreateImpl<DerivedType>, this BOOST_PP_REPEAT(N, GENERIC_FACTORY_APPEND_PLACEHOLDER, N)); 
      } 

      //Deregisters an existing registration. 
      bool Deregister(const KeyType& key) 
      { 
       return (m_creatorMap.erase(key) == 1); 
      } 

      //Returns true if the key is registered in this factory, false otherwise. 
      bool IsCreatable(const KeyType& key) const 
      { 
       return (m_creatorMap.count(key) != 0); 
      } 

      //Creates the derived type associated with key. Throws std::out_of_range if key not found. 
      BasePointerType Create(const KeyType& key BOOST_PP_ENUM_TRAILING_BINARY_PARAMS(N,const T,& a)) const 
      { 
       return m_creatorMap.at(key)(BOOST_PP_ENUM_PARAMS(N,a)); 
      } 

     private: 
      //This method performs the creation of the derived type object on the heap. 
      template <class DerivedType> 
      BasePointerType CreateImpl(BOOST_PP_ENUM_BINARY_PARAMS(N,const T,& a)) 
      { 
       BasePointerType pNewObject(new DerivedType(BOOST_PP_ENUM_PARAMS(N,a))); 
       return pNewObject; 
      } 

     private: 
      typedef std::function<BasePointerType (BOOST_PP_ENUM_BINARY_PARAMS(N,const T,& BOOST_PP_INTERCEPT))> CreatorFuncType; 
      typedef std::unordered_map<KeyType, CreatorFuncType> CreatorMapType; 
      CreatorMapType m_creatorMap; 
    }; 

    #undef N 
    #undef GENERIC_FACTORY_APPEND_PLACEHOLDER 

#endif // defined(BOOST_PP_IS_ITERATING) 
#endif // include guard 

私は一般的にマクロの大量使用に反対していますが、ここでは例外を作成しました。上記のコードは、GenericFactory_NというクラスのGENERIC_FACTORY_MAX_ARITY + 1バージョンを0からGENERIC_FACTORY_MAX_ARITYまでの各Nに対して生成します。

生成されたクラステンプレートを使用するのは簡単です。ファクトリで文字列マッピングを使用してBaseClassから派生したオブジェクトを作成するとします。派生オブジェクトのそれぞれは、コンストラクタパラメータとして3つの整数を取ります。

#include "GenericFactory.hpp" 

typedef GenericFactory_3<std::string, std::shared_ptr<BaseClass>, int, int int> factory_type; 

factory_type factory; 
factory.Register<DerivedClass1>("DerivedType1"); 
factory.Register<DerivedClass2>("DerivedType2"); 
factory.Register<DerivedClass3>("DerivedType3"); 

factory_type::result_type someNewObject1 = factory.Create("DerivedType2", 1, 2, 3); 
factory_type::result_type someNewObject2 = factory.Create("DerivedType1", 4, 5, 6); 

GenericFactory_Nクラスのデストラクタは、以下を可能にするためにvirtualです。

class SomeBaseFactory : public GenericFactory_2<int, BaseType*, std::string, bool> 
{ 
    public: 
     SomeBaseFactory() : GenericFactory_2() 
     { 
      Register<SomeDerived1>(1); 
      Register<SomeDerived2>(2); 
     } 
}; 

SomeBaseFactory factory; 
SomeBaseFactory::result_type someObject = factory.Create(1, "Hi", true); 
delete someObject; 

汎用工場発生のこのラインマクロ

#define BOOST_PP_FILENAME_1 "GenericFactory.hpp" 

汎用工場ヘッダファイルはGenericFactory.hpp

1
--------------- 
Detail solution for registering the objects, and accessing them with string names. 
--------------- 
1. common.h 
#ifndef COMMON_H_ 
#define COMMON_H_ 


#include<iostream> 
#include<string> 
#include<iomanip> 
#include<map> 

using namespace std; 
class Base{ 
public: 
    Base(){cout <<"Base constructor\n";} 
    virtual ~Base(){cout <<"Base destructor\n";} 
}; 
#endif /* COMMON_H_ */ 

2. test1.h 
/* 
* test1.h 
* 
* Created on: 28-Dec-2015 
*  Author: ravi.prasad 
*/ 

#ifndef TEST1_H_ 
#define TEST1_H_ 
#include "common.h" 

class test1: public Base{ 
    int m_a; 
    int m_b; 
public: 
    test1(int a=0, int b=0):m_a(a),m_b(b) 
    { 
     cout <<"test1 constructor m_a="<<m_a<<"m_b="<<m_b<<endl; 
    } 
    virtual ~test1(){cout <<"test1 destructor\n";} 
}; 



#endif /* TEST1_H_ */ 

3. test2.h 
#ifndef TEST2_H_ 
#define TEST2_H_ 
#include "common.h" 

class test2: public Base{ 
    int m_a; 
    int m_b; 
public: 
    test2(int a=0, int b=0):m_a(a),m_b(b) 
    { 
     cout <<"test1 constructor m_a="<<m_a<<"m_b="<<m_b<<endl; 
    } 
    virtual ~test2(){cout <<"test2 destructor\n";} 
}; 


#endif /* TEST2_H_ */ 

3. main.cpp 
#include "test1.h" 
#include "test2.h" 

template<typename T> Base * createInstance(int a, int b) { return new T(a,b); } 

typedef std::map<std::string, Base* (*)(int,int)> map_type; 

map_type mymap; 

int main() 
{ 

    mymap["test1"] = &createInstance<test1>; 
    mymap["test2"] = &createInstance<test2>; 

    /*for (map_type::iterator it=mymap.begin(); it!=mymap.end(); ++it) 
     std::cout << it->first << " => " << it->second(10,20) << '\n';*/ 

    Base *b = mymap["test1"](10,20); 
    Base *b2 = mymap["test2"](30,40); 

    return 0; 
} 

------------------------ 
Compile and Run it (Have done this with Eclipse) 
------------------------ 
/Output 

Base constructor 
test1 constructor m_a=10m_b=20 
Base constructor 
test1 constructor m_a=30m_b=40 
関連する問題