これは、実行時に解決されるファクトリを管理するための持続可能なイディオムです。私は過去にこれを使用して、かなり洗練された動作をサポートしました。私は、機能性をあまり犠牲にすることなく、シンプルさと保守性を重視しています。
TLDR:
- 一般的に静的初期化を避ける
- ペストのような「自動読み込み」テクニックを避ける
- オブジェクトとファクトリの所有権を伝達する
- 使い分けと工場管理の悩み
ランタイム ファクトリの使用
これは、このファクトリ システムのユーザーが操作する基本インターフェイスです。工場の詳細について心配する必要はありません。
class BaseObject {
public:
virtual ~BaseObject() {}
};
BaseObject* CreateObjectFromStream(std::istream& is);
余談ですが、生のポインターの代わりに参照、、boost::optional
またはを使用することをお勧めします。shared_ptr
完璧な世界では、このオブジェクトの所有者をインターフェイスが教えてくれるはずです。ユーザーとして、このポインターが与えられたときに削除する責任はありますか? それがshared_ptr
.
ランタイム ファクトリの実装
別のヘッダーに、ファクトリがアクティブな場合のスコープの管理の詳細を入れます。
class RuntimeFactory {
public:
virtual BaseObject* create(std::istream& is) = 0;
};
void RegisterRuntimeFactory(RuntimeFactory* factory);
void UnregisterRuntimeFactory(RuntimeFactory* factory);
これらすべての重要な点は、ファクトリがどのように初期化および使用されるかとは異なる問題であるということです。
これらの無料関数の呼び出し元がファクトリを所有していることに注意してください。レジストリはそれらを所有していません。
これは厳密には必要ではありませんが、これらの工場がいつどこで破壊されるかをより詳細に制御できます。重要なのは、「作成後」または「破棄前」の呼び出しなどを目にするときです。このような名前のファクトリ メソッドは、所有権の逆転の設計上の匂いです。
とにかく、ファクトリのライフタイムを管理するためにこれに別のラッパーを書くのは簡単です。また、構成にも役立ちます。これは優れています。
新しい工場を登録する
ファクトリ登録ごとにラッパーを作成します。私は通常、各工場登録を独自のヘッダーに入れます。これらのヘッダーは通常、2 つの関数呼び出しだけです。
void RegisterFooFactory();
void UnregisterFooFactory();
これはやり過ぎのように思えるかもしれませんが、この種の勤勉さによってコンパイル時間が短縮されます。
私のmain
その後は、一連の登録および登録解除の呼び出しに削減されます。
#include <foo_register.h>
#include <bar_register.h>
int main(int argc, char* argv[]) {
SetupLogging();
SetupRuntimeFactory();
RegisterFooFactory();
RegisterBarFactory();
// do work...
UnregisterFooFactory();
UnregisterBarFactory();
CleanupLogging();
return 0;
}
静的初期化の落とし穴を避ける
これにより、他のソリューションのように静的読み込み中に作成されたオブジェクトが特に回避されます。これは事故 ではありません。
- C++ の仕様では、静的な読み込みがいつ発生するかについて有用な保証が得られません。
- 何か問題が発生すると、スタック トレースが取得されます
- コードはシンプルで、直接的で、従うのが簡単です
レジストリの実装
ご想像のとおり、実装の詳細はかなりありふれたものです。
class RuntimeFactoryRegistry {
public:
void registerFactory(RuntimeFactory* factory) {
factories.insert(factory);
}
void unregisterFactory(RuntimeFactory* factory) {
factories.erase(factory);
}
BaseObject* create(std::istream& is) {
std::set<RuntimeFactory*>::iterator cur = factories.begin();
std::set<RuntimeFactory*>::iterator end = factories.end();
for (; cur != end; cur++) {
// reset input?
if (BaseObject* obj = (*cur)->create(is)) {
return obj;
}
}
return 0;
}
private:
std::set<RuntimeFactory*> factories;
};
これは、すべてのファクトリが相互に排他的であることを前提としています。この仮定を緩和しても、ソフトウェアが適切に動作することはまずありません。私はおそらく個人的により強い主張をするでしょう、へへ。もう 1 つの方法は、オブジェクトのリストを返すことです。
以下の実装は、デモを簡単にするために静的です。これは、マルチスレッド環境では問題になる可能性があります。静的である必要はありません。また、静的であるべきかどうかもお勧めしません。ここにあるだけです。議論の本題ではないので、このままにしておきます。
これらのフリー関数は、この実装のパススルー関数としてのみ機能します。これにより、レジストリを単体テストしたり、必要に応じて再利用したりできます。
namespace {
static RuntimeFactoryRegistry* registry = 0;
} // anon
void SetupRuntimeFactory() {
registry = new RuntimeFactoryRegistry;
}
void CleanupRuntimeFactory() {
delete registry;
registry = 0;
}
BaseObject* CreateObjectFromStream(std::istream& is) {
return registry->create(is);
}
void RegisterRuntimeFactory(RuntimeFactory* factory) {
registry->registerFactory(factory);
}
void UnregisterRuntimeFactory(RuntimeFactory* factory) {
registry->unregisterFactory(factory);
}