共通のヘッダーファイル(インクルード)で宣言したC++関連のクラス(すべて同じ基本クラスを拡張し、同じコンストラクターを提供する)と、他のいくつかのファイル(静的にコンパイルおよびリンクする)での実装があると想像してください。私のプログラムのビルドの一部として)。
名前を渡してそれらの1つをインスタンス化できるようにしたいと思います。これは、プログラムに(コマンドラインまたはコンパイルマクロとして)渡さなければならないパラメーターです。
私が見る唯一の可能な解決策は、マクロを使用することです。
#ifndef CLASS_NAME
#define CLASS_NAME MyDefaultClassToUse
#endif
BaseClass* o = new CLASS_NAME(param1, param2, ..);
それが唯一の価値あるアプローチですか?
これは、 レジストリパターン を使用して一般的に解決される問題です。
これは、レジストリパターンが説明する状況です。
オブジェクトは別のオブジェクトに接続する必要があります。オブジェクトの名前またはオブジェクトが提供するサービスの名前のみを知っており、接続方法は知っていません。オブジェクト、サービス、またはロールの名前を取り、名前付きオブジェクトへの接続方法の知識をカプセル化したリモートプロキシを返すサービスを提供します。
これは、サービス指向アーキテクチャー(SOA)およびOSGiのサービスレイヤーの基礎を形成するのと同じ基本的な公開/検索モデルです。
通常はシングルトンオブジェクトを使用してレジストリを実装します。シングルトンオブジェクトは、コンパイル時または起動時にオブジェクトの名前とその構築方法を通知されます。次に、それを使用して、オンデマンドでオブジェクトを作成できます。
例えば:
template<class T>
class Registry
{
typedef boost::function0<T *> Creator;
typedef std::map<std::string, Creator> Creators;
Creators _creators;
public:
void register(const std::string &className, const Creator &creator);
T *create(const std::string &className);
}
オブジェクトの名前と作成関数を次のように登録します。
Registry<I> registry;
registry.register("MyClass", &MyClass::Creator);
std::auto_ptr<T> myT(registry.create("MyClass"));
次に、これを巧妙なマクロで単純化して、コンパイル時に実行できるようにします。 [〜#〜] atl [〜#〜] は、実行時に名前で作成できるCoClassesのレジストリパターンを使用します-登録は、次のコードのようなものを使用するのと同じくらい簡単です。
OBJECT_ENTRY_AUTO(someClassID, SomeClassName);
このマクロはヘッダーファイルのどこかに配置されます。マジックにより、COMサーバーの起動時にシングルトンに登録されます。
これを実装する方法は、クラス 'names'からファクトリ関数へのマッピングをハードコーディングすることです。テンプレートを使用すると、コードが短くなる場合があります。 STLを使用すると、コーディングが簡単になる場合があります。
#include "BaseObject.h"
#include "CommonClasses.h"
template< typename T > BaseObject* fCreate( int param1, bool param2 ) {
return new T( param1, param2 );
}
typedef BaseObject* (*tConstructor)( int param1, bool param2 );
struct Mapping { string classname; tConstructor constructor;
pair<string,tConstructor> makepair()const {
return make_pair( classname, constructor );
}
} mapping[] =
{ { "class1", &fCreate<Class1> }
, { "class2", &fCreate<Class2> }
// , ...
};
map< string, constructor > constructors;
transform( mapping, mapping+_countof(mapping),
inserter( constructors, constructors.begin() ),
mem_fun_ref( &Mapping::makepair ) );
編集-一般的な要求に応じて:)物事をよりスムーズに見せるために少し手直しします(おそらく自分で答えを追加したくないStoneFreeのクレジット)
typedef BaseObject* (*tConstructor)( int param1, bool param2 );
struct Mapping {
string classname;
tConstructor constructor;
operator pair<string,tConstructor> () const {
return make_pair( classname, constructor );
}
} mapping[] =
{ { "class1", &fCreate<Class1> }
, { "class2", &fCreate<Class2> }
// , ...
};
static const map< string, constructor > constructors(
begin(mapping), end(mapping) ); // added a flavor of C++0x, too.
オブジェクトファクトリを使用してみませんか?
最も単純な形式:
BaseClass* myFactory(std::string const& classname, params...)
{
if(classname == "Class1"){
return new Class1(params...);
}else if(...){
return new ...;
}else{
//Throw or return null
}
return NULL;
}
C++では、この決定はコンパイル時に行う必要があります。
コンパイル時に、macorではなくtypedefを使用できます。
typedef DefaultClass MyDefaultClassToUse;
これは同等であり、マクロを回避します(マクロが悪い;-))。
実行時に決定を下す場合は、それをサポートする独自のコードを作成する必要があります。単純なソリューションは、文字列をテストし、それぞれのクラスをインスタンス化する関数です。
その拡張バージョン(独立したコードセクションがクラスを登録できるようにする)はmap<name, factory function pointer>
。
コマンドラインとコンパイルマクロの2つの可能性について言及しましたが、それぞれの解決策は大きく異なります。
選択がコンパイルマクロによって行われる場合、それは#definesや#ifdefsなどで解決できる単純な問題です。あなたが提案する解決策はどれよりも優れています。
ただし、コマンドライン引数を使用して実行時に選択を行う場合は、文字列を受信して適切なオブジェクトを作成できるFactoryフレームワークが必要です。これは、すべての可能性を備えた単純な静的if().. else if()... else if()...
チェーンを使用して実行できます。または、オブジェクトが登録され、複製されて新しいインスタンスを提供する完全に動的なフレームワークにすることもできます。
以前は、ファクトリ自体がクラスについて具体的に知らなくても、実行時にクラスが自己登録できるようにファクトリパターンを実装していました。重要なのは、(IIRC)「初期化によるアタッチメント」と呼ばれる非標準のコンパイラ機能を使用することです。この機能では、各クラス(boolなど)の実装ファイルでダミーの静的変数を宣言し、登録を呼び出して初期化します。ルーチン。
このスキームでは、各クラスはそのファクトリを含むヘッダーを#includeする必要がありますが、ファクトリはインターフェイスクラス以外は何も知りません。ビルドから実装クラスを文字通り追加または削除し、コードを変更せずに再コンパイルできます。
キャッチは、一部のコンパイラのみが初期化による添付をサポートしていることです-IIRCの他のコンパイラは、最初の使用時にファイルスコープ変数を初期化します(関数ローカル統計が機能するのと同じ方法です)。常に空であることがわかります。
しかし、私が興味を持っているコンパイラ(MSVCとGCC)はこれをサポートしているので、私にとっては問題ではありません。このソリューションが自分に適しているかどうかを自分で判断する必要があります。