1. ホーム
  2. c++

[解決済み] クラス名を持つ文字列からオブジェクトをインスタンス化する方法はありますか?

2022-04-28 03:31:40

質問

ファイルを持っています。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++でも同じようなことができるのでしょうか?

どのように解決するのですか?

いや、自分でマッピングをしない限りは無理だ。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, 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 .