3

1 つのスーパークラスの複数のサブクラスがあり、特定の文字列に従って特定のクラスのインスタンスを作成したい

Superclass instantiateSubclass(string s);

巨大な if-cascade を使用する代わりに、構成ファイルを使用してこれを実現したいと考えています。

これにより、再コンパイルせずに string の可能な値を変更できるようになり、より簡潔なコードにつながることを願っています。

構成ファイルには「subclass1」、「subclass2」などの文字列が含まれている必要がありますが、文字列に従ってクラスを作成するにはどうすればよいですか?

基本的に、文字列からクラスへのマッピングが必要ですが、これは C++ で可能ですか? 他の言語は、この問題に対するリフレクションのような可能性を提供すると思います。

4

4 に答える 4

10

クラスを登録します。

struct Base;

struct Derived1 : Base
{
    static Base * create() { return new Derived1; }
};

std::map<std::string, Base * (*)()> registry = { {"derived1", &Derived1::create},
                                                 /* ... */
                                               };

作る:

Base * create_from_string(std::string const & s)
{
    auto it = registry.find(s);
    return it == registry.end() ? nullptr : (it->second)();
}
于 2013-01-24T18:27:08.203 に答える
3

私は以前に似たようなことをしました。文字列(型)/関数ポインタ(ファクトリ)のペアのunordered_mapを作成します。それぞれが、その型のインスタンスを作成する静的関数を指しています。

これでも、これらの小さなスタブファクトリ関数が必要です。通常、これらはそのタイプを作成するワンライナーです。マクロを使用して、ファクトリメソッドを生成できます。

typedef std::unordered_map<std::string, Superclass *(*)()> TypeDirectory;
TypeDirectory types;

#define NAMEFACTORY(name_) static Superclass *Create() { return new name_; }

class Hello : public Superclass
{
   ...

   NAMEFACTORY(Hello)
};

static void RegisterFactoryNames()
{
  types.emplace_back("Hello", &Hello::Create);
  ...
}

static Superclass *MakeInstance(std::string &name)
{
  auto i = types.find(name);
  return i != types.end() ? types->second() : 0;
}

ファクトリデータの名前がどこにあるかを知っているのはあなただけです。私の例では、データを「中に」入れていません。

注:MSVC 10以下(2010以下)を使用している場合types.push_back(TypeDirectory::value_type("Hello", &Hello::Create));は、emplace_backの実装が完全に正しくないため、これらのバージョンを使用してください。

于 2013-01-24T18:27:32.470 に答える
1

次のコードを使用すると、ベースから派生した任意の数のクラスを登録INTERFACEし、それらを から返された文字列でインスタンス化できますregister_class

欠点は、プラットフォーム間でキーが異なる可能性があることであり、構成ファイルに正しいクラスキーを確実に入れるために、各クラスに対して typeid(CLASS).name() が返すものを知る必要があります (そうではない場合があります)。クラス名と同じ)

typeid の詳細を読んで、ここで何が起こっているかを確認してください

   template <class INTERFACE> class factory
    {
    public:
        template <class CLASS> const std::string register_class()
        {
            static const std::string key = typeid(CLASS).name();
            class class_factory : public ifactory
            {
            private:
                virtual std::shared_ptr<INTERFACE> create() const
                {
                    return new CLASS;
                }
            };
            m_factory_map[key] = new class_factory;
            return key;
        }
        std::shared_ptr<INTERFACE> create(const std::string& key) const
        {
            const factory_map::const_iterator ifind = m_factory_map.find(key);
            if(ifind == m_factory_map.end())
                return 0;
            return ifind->second->create();
        }
    private:
        class ifactory
        {
        public:
            virtual ~ifactory() {}
            virtual std::shared_ptr<INTERFACE> create() const = 0;
        };
        typedef std::map<std::string, std::shared_ptr<ifactory> > factory_map;
        factory_map m_factory_map;
    };

このように使用してください

factory<MyInterface> fact;
const std::string key1 = fact.register_class<MyClass1>();
const std::string key2 = fact.register_class<MyClass2>();
const std::string key3 = fact.register_class<MyClass3>();
std::shared_ptr<MyInterface> p1 = fact.create(key1);
std::shared_ptr<MyInterface> p2 = fact.create(key2);
std::shared_ptr<MyInterface> p3 = fact.create(key3);
于 2013-01-24T18:40:10.293 に答える
0

設定ファイルを使用するかどうかに関係なく、ある時点で、文字列を「新しいsomeclass」呼び出し[またはそれらの行に沿った何か]に変換する必要があります。これには、文字列を比較し、新しいアイテムを選択することが含まれます。if / else if / else if ...の長いチェーン、またはテーブルのいずれか。テーブルの2番目のエントリのoufを取得した整数定数に基づいて、switchステートメントで選択を行うことができます。

もう1つの可能性は、もちろん、問題を完全に別のレベルに移動し、クラスごとに共有ライブラリを実装し、その名前に基づいて関連するライブラリをロードしてから、共有ライブラリに定義された名前/序数の機能を持たせることです。あなたは「私をあなたの種類の物の1つにする」と呼ぶことができます。

于 2013-01-24T18:27:24.747 に答える