C ++の静的コンストラクターを模倣します
-
24-10-2019 - |
質問
これは、C ++のオブジェクトの初期化に関連する質問です
私は、一般的な基本クラスから継承するクラスのグループ(インスタンスではありません)を持っています。プログラムの開始時にコンテナ(特にマップ)に自分に関する情報を登録する必要があります。
問題は、私がそれをダイナミックにする必要があることです。コンテナは、クラスとは異なる独立したプロジェクトで定義されています。ライブラリの複数のハードコーディングバージョンの作成を避けることをお勧めします。これは、各プログラムの各クラスのセットごとに使用する各セットに1つずつ使用します。
これらの各サブクラスに特別なクラスの静的なインスタンスがあることを考えました。これにより、コンストラクターに登録が行われます。ただし、これらのオブジェクトの構築前にコンテナが構築されたことを保証する方法は見つかりませんでした。
また、これらのサブクラスのインスタンスが作成される前に、サブクラスに関するコンテナ内の情報を利用できるようにする必要があることに注意する必要があります。
これを行う方法はありますか、それともC ++一般的に静的コンストラクターを模倣しますか?
解決
あなたは一度にさまざまな問題を説明しています。ある種のものを持つという特定の問題について 静的初期化, 、簡単なアプローチは、登録を実行する偽のクラスを作成することです。その後、異なるクラスのそれぞれが static const X
メンバー、メンバーは翻訳ユニットで定義する必要があり、定義はインスタンスのインスタンス化とクラスの登録をトリガーします。
これは難しい問題に取り組むことはありません。これは、初期化の順序である大失敗です。言語は、異なる翻訳ユニットのオブジェクトの初期化の順序に関する保証を提供しません。つまり、そのようなクラスで3つの翻訳ユニットをコンパイルする場合、偽のメンバーの相対的な実行順序について保証はありません。ライブラリにも適用されます。クラスを登録するコンテナが初期化されているという保証はありません。
コードにアクセスできる場合は、使用するコンテナコードを変更できます static local variables
, 、それは初期化の順序を確保するための一歩となります。可能な解決策のスケッチとして:
// registry lib
class registry { // basically a singleton
public:
static registry& instance() { // ensures initialization in the first call
static registry inst;
return inst;
}
// rest of the code
private:
registry(); // disable other code from constructing elements of this type
};
// register.h
struct register {
template <typename T>
register( std::string name ) {
registry::instance().register( name, T (*factory)() ); // or whatever you need to register
}
};
// a.h
class a {
public:
static a* factory();
private:
static const register r;
};
// a.cpp
const register a::r( "class a", a::factory );
// b.h/b.cpp similar to a.h/a.cpp
この場合、登録中に明確な順序はありません a
と b
クラスですが、それは問題ではないかもしれません。一方、aを使用して ローカル静的変数 の中に registry::instance
関数シングルトンの初期化が実行されることが保証されています 前 の呼び出し registry::register
方法(最初の呼び出しの一部として instance
方法)。
あなたがその変更をすることができないなら、あなたは基本的に運があり、あなたはそれを保証することはできません registry
他の翻訳ユニットの他の静的メンバー属性(またはグローバル)の前にインスタンス化されます。その場合、クラスの登録を最初のインスタンス化に延期し、クラスが登録されるように登録される各クラスのコンストラクターにコードを追加する必要があります。 前 オブジェクトの実際の構築。
これは、他のコードがタイプのオブジェクトを作成するかどうかに応じて、解決策であるかどうかです。工場関数の特定のケース(最初に思い浮かびました)では、タイプのオブジェクトを作成することが許可されていない場合は、 a
また b
...その後、コンストラクターコールの貯金孔登録も解決策ではありません。
他のヒント
OOPパラダイムに反対していますが、静的メンバーに2つのグローバル変数に導かれたリンクリストを形成するのはどうですか?あなたはそのようなことをすることができます:
ClassRegistrator *head=NULL;
ClassRegistrator *tail=NULL;
struct ClassRegistrator {
... //data that you need
ClassRegistrator *next;
ClassRegistrator(classData ...) {
if (head==NULL) head=tail=this;
else {
tail->next=this;
tail=this;
}
... //do other stuff that you need for registration
}
};
class MyClass { //the class you want to register
static ClassRegistrator registrator;
}
ClassRegistrator MyClass::registrator(...); //call the constructor
私 信じる グローバル変数は、 コンストラクタ, 、しかし、純粋なデータであるため、コードの実行を開始すると、すでに初期化されていることが保証されています。
明らかにこれはスレッドセーフなどではありませんが、仕事を終わらせる必要があります。
これは候補者です シングルトンパターン. 。基本的に、サブクラスの最初のインスタンスがインスタンス化されたときに、コンテナをインスタンス化する必要があります。これは、シングルトンポインターがベースクラスコンストラクターのヌルであるかどうかを確認することで促進できます。
1つのアイデアは、渡すことです 登録 クラスのfunctor。各子孫は、登録する関数を実行します。この機能は、コンストラクターに渡すことができます。
例:
struct Registration_Interface
{
virtual void operator() (const std::string& component_name) = 0;
};
struct Base
{
};
struct Child1
: public Base
{
Child(Registration_Interface& registration_ftor)
{
//...
registration_ftor("Child1");
}
};
見る: http://www.parashift.com/c++faq-lite/ctors.html#faq-10.14
1つの選択肢は、最初のものが追加されたときに、コンテナを怠lazに構築することです。
void AddToContainer(...) {
// Will be initialized the first time this function is called.
static Container* c = new Container();
c->Add(...);
}
静的コンストラクターを「模倣」する唯一の方法は、関数を明示的に呼び出して静的初期化を実行することです。モジュールをリンクするだけで、コードを実行する他の方法はありません。
「最初の使用の初期化」パターンを使用してから、ダミー静的インスタンスをインスタンスにして、できるだけ早く初期化を確認できます。
class cExample
{
public :
cExample() ;
// Static functions here
private :
static bool static_init ;
// other static members here
}
cExample::static init = false ;
cExample::cExample()
{
// Static initialisation on first use
if( !static_init )
{
// initialise static members
}
// Instance initialisation here (if needed)
}
// Dummy instance to force initialisation before main() (if necessary)
static cExample force_init ;