クラス名を保持する文字列からオブジェクトをインスタンス化する方法はありますか?


143

私はファイルを持っています: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#で実行できると思います。C ++にも同様のものはありますか?


その部分的に可能C ++ 0xのと可変引数テンプレートと...
smerlin

回答:


227

いいえ、自分でマッピングを行わない限り、何もありません。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つのより良い名前があると確信しています。ここでおそらく使用する意味のあるもう1つのことは、shared_ptrです。

共通の基本クラスを持たない無関係な型のセットがある場合は、boost::variant<A, B, C, D, ...>代わりに関数ポインターに戻り型を与えることができます。クラスFoo、Bar、Bazがある場合、次のようになります。

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;

A boost::variantは組合のようなものです。初期化または割り当てに使用されたオブジェクトを調べることで、どのタイプが格納されているかを認識します。こちらのドキュメントをご覧ください。最後に、生の関数ポインタの使用も少し古いです。最新のC ++コードは、特定の関数/型から分離する必要があります。あなたはBoost.Functionより良い方法を探すために調べたくなるかもしれません。この場合、次のようになります(マップ)。

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

std::functionを含め、C ++の次のバージョンでも使用できるようになりますstd::shared_ptr


3
派生クラスが自分自身を登録するという考えが気に入りました。それはまさに私が探していたものであり、どの派生クラスが存在するかについてのハードコードされた知識をファクトリーから削除する方法です。
Galゴールドマン、

1
もともと別の質問でsomedaveによって投稿されたこのコードは、VS2010でmake_pairのためにあいまいなテンプレートエラーで失敗します。修正するには、make_pairをstd :: pair <std :: string、Base *()()>に変更すると、これらのエラーが修正されます。BaseFactory :: map_type BaseFactory :: map = new map_type();を追加することで修正されたいくつかのリンクエラーも発生しました。base.cppへ
スペンサーローズ

9
DerivedB::reg実際に初期化されていることをどのように確認しますか?derivedb.cpp3.6.2のように、翻訳単位で関数やオブジェクトが定義されていない場合は、まったく構築されない可能性があると私は理解しています。
musiphil 2012

2
自己登録が大好きです。コンパイルするにBaseFactory::map_type * BaseFactory::map = NULL;は、cppファイルにが必要でした 。これがないと、リンカーは不明なシンボルマップについて不平を言っていました。
2016

1
残念ながら、これは機能しません。musiphilがすでに指摘したように、DerivedB::regその機能やインスタンスが翻訳単位で定義されていない場合は初期化されませんderivedb.cpp。つまり、クラスは実際にインスタンス化されるまで登録されません。誰かがその回避策を知っていますか?
Tomasito665 2016年

7

いいえ、ありません。この問題に対する私の好ましい解決策は、名前を作成方法にマップする辞書を作成することです。このように作成したいクラスは、作成メソッドを辞書に登録します。これについては、GoFパターンブックである程度詳しく説明しています。


5
本を指すだけでなく、これがどのパターンであるかを特定することに関心がある人はいますか?
josaphatv 2014年

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

2
今、この回答を読んでいる人にとって、回答は、インスタンス化するクラスを決定するためにディクショナリを使用する実装であるFactoryパターンの使用に言及していると思います。
Grimeh 2015年


4

C ++ファクトリーに関する別のSOの質問で答えました。フレキシブルな工場に興味がある方はそちらをご覧ください。私はET ++からマクロを使用するための古い方法を説明しようとしていますが、これは私にとってはうまくいきました。

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


2

boost :: functionalには、非常に柔軟なファクトリテンプレートがあります。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

私は一般的にマクロの多用に反対していますが、ここでは例外を設けました。上記のコードは、GENERIC_FACTORY_MAX_ARITY + 1バージョンのクラスのGenericFactory_Nを生成します。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クラスデストラクタは仮想であり、以下を許可します。

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であると想定します。


2

オブジェクトを登録し、文字列名でアクセスするための詳細なソリューション。

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_ */

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_ */

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

コンパイルして実行します(Eclipseでこれを実行しました)

出力:

Base constructor
test1 constructor m_a=10m_b=20
Base constructor
test1 constructor m_a=30m_b=40


1

Tor Brede Vekterliは、求める機能を正確に提供するブースト拡張を提供します。現在、それは現在のブーストライブラリと少しぎこちないフィッティングですが、ベース名前空間を変更した後、1.48_0で動作させることができました。

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

(リフレクションとして)そのようなことがc ++に役立つ理由を質問する人への回答-私はUIとエンジンの間の相互作用にそれを使用します-ユーザーはUIのオプションを選択し、エンジンはUI選択文字列を取ります、そして、目的のタイプのオブジェクトを生成します。

ここでフレームワークを使用する主な利点は(どこかでフルーツリストを維持することより)、登録関数が各クラスの定義に含まれていること(そして、登録されたクラスごとに1行のコードで登録関数を呼び出すだけでよい)です。フルーツリスト。新しいクラスが派生するたびに手動で追加する必要があります。

ファクトリーを基本クラスの静的メンバーにしました。


0

これはファクトリーパターンです。ウィキペディア(およびこの例)を参照してください。悪質なハックなしでは文字列からタイプ自体を作成することはできません。なぜこれが必要なのですか?


ファイルから文字列を読み取るため、これが必要です。これがある場合は、ファクトリを非常に汎用的にすることができるため、正しいインスタンスを作成するために何も知る必要はありません。これは非常に強力です。
Galゴールドマン、

それで、バスと車は両方とも車両なので、バスと車に異なるクラス定義は必要ないでしょうか?ただし、そうする場合、別の行を追加することは実際には問題になりません:)マップのアプローチにも同じ問題があります-マップのコンテンツを更新します。マクロThingは簡単なクラスで機能します。
2009

私の場合、バスまたは車を作成するために、別の定義は必要ありません。それ以外の場合、ファクトリーデザインパターンは使用されません。私の目標は、工場をできるだけ愚かにすることでした。しかし、ここには逃げ道がないことがわかります:-)
Gal Goldman
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.