class があるとしImage
ます。解析のある時点で、「Image」が適切なタイミングで読み込まれます。つまり、 class のオブジェクトを作成したいということですImage
。
私が考えているのは、これらの文字列を適切なクラスへのコンストラクター呼び出しにマッピングすることですが、これを達成する方法がわかりません。
いえ
container.push_back( some_map[stringParsedIn] ); // basic idea
Stephen が指摘したように、記述しているのは Factory パターンです (Image が抽象基本クラスであると仮定します)。ただし、その実装では、if/else ステートメントで構成される大きな関数の代わりに、説明したように文字列を作成関数に関連付けると役立つ場合があります。これを行う 1 つの方法を次に示します (イメージ サブクラスがすべて同じ方法で構築できると仮定します)。
typedef Image* create_image_function();
template <class T>
Image* create_image(SomeType arg)
{
return new T(arg);
}
...
map<string, create_image_function*> creators;
creators["Foo"] = &create_image<Foo>;
creators["Bar"] = &create_image<Bar>;
creators["Baz"] = &create_image<Baz>;
shared_ptr<Image> ImageFactory::make_image(const string& str)
{
// checking to see if str exists as a key
// would be nice
return shared_ptr<Image>(creators[str](arg));
}
ファクトリ関数を記述しています。これを実現するには、レジストリから単純なif/else
チェーンまで、さまざまな方法があります。
通常、Image
クラスは、他の「解析される」型と同様の基本クラスから派生します。そうすれば、それらすべてを同じコンテナーに追加できます。
次の階層を想像してください。
class Media {
public:
virtual Save() = 0;
};
class Image : public Media {
public:
Image() { }
virtual Save() { ... }
};
class Sound : public Media {
public:
Sound() { }
virtual Save() { ... }
};
最も単純な構成は、ファクトリ関数です。
Media *CreateMedia(const string &type) {
if (type == "Image") {
return new Image;
} else if (type == "Sound") {
return new Sound;
} else {
// handle invalid type error
}
}
もう 1 つの方法は、レジストリを使用するCreateMedia
ことです。通常、マクロ、ファクトリ/レジストリ、およびいくつかのメカニズムを使用してサブクラスを作成する代わりに、次のようにします。
// This is some mechanism to create types of Media.
template <typename T>
struct CreatorFunction {
Media *operator() {
return new T;
}
};
// This is the factory that the types will register with.
class Factory {
public:
// singleton access function.
static Factory* Get() {
static Factory* f = new Factory;
return f;
}
// Creates Media of the given type.
Media* Create(const string& name) { return registry_[name](); }
// Records 'name' with the creator function 'func'.
void Add(const string& name, const CreatorFunction &func) {
registry_.insert(name, func);
}
private:
Factory() { } // users can't create factories, they can only use singleton.
hash_map<string, CreatorFunction> registry_;
};
#define REGISTER_MEDIA(type) Factory::Get()->Add(#type, CreatorFunction<type>);
REGISTER_MEDIA(Image); // usually goes with the Image class.
REGISTER_MEDIA(Sound); // usually goes with the Sound class.
int main(int argc, char** argv) {
string parsedIn = "Image";
Factory::Get()->Create(parsedIn);
}
これは全体的によりクリーンなアプローチですが、一部のシンボルが使用されていないとリンカが判断し、重要な登録済みクラスをバイナリから削除するという問題が発生する可能性があります。if/then
より洗練されたものが必要になるまで、おそらく連鎖を使い続けたいと思うでしょう。すべてのサブタイプが定義されている単一の場所を持つことが不可能な場合は、通常、レジストリを使用します。
関数ポインターをコンストラクターに格納することはできませんが、新しく構築されたオブジェクトを返す関数へのポインターを格納することはできます。
Image *createImage() {
return new Image();
}
次に、この関数へのポインターをマップに格納できます。
std::map<std::string, Image *(*)()> constructorMap;
constructorMap.insert(std::pair<std::string, Image *(*)()>("Image", createImage));
そして、それを呼び出す
Image *myImage = constructorMap["Image"]();
あなたが何を求めているのか 100% 確信があるわけではありませんが、推測してみましょう。
コンストラクターを関数でラップできます。
Image* makeImage(ArgType arg) { return new Image(arg); }
そして、関数ポインタをマップに格納できます!
map["Image"] = makeImage;
後でそれらを呼び出すために!
SuperclassOfImage soup = map["Image"](arg);
もちろん、ここでの制限は、関数の型シグネチャが同じ型引数を取り、同じ型 (Image または Image の親であるクラスのインスタンス) を返さなければならないことです。