C++で静的変数を使用するとき、しばしば1つの変数を初期化して別の変数をコンストラクターに渡したいと思うようになります。つまり、相互に依存する静的インスタンスを作成する必要があります。
単一の.cppまたは.hファイル内では、これは問題ではありません。インスタンスは宣言された順序で作成されます。ただし、静的インスタンスを別のコンパイル単位のインスタンスで初期化する場合、順序を指定することは不可能なようです。その結果、天候に応じて、他のインスタンスに依存するインスタンスが構築され、その後にのみ他のインスタンスが構築される可能性があります。その結果、最初のインスタンスが誤って初期化されます。
静的オブジェクトが正しい順序で作成されるようにする方法を知っている人はいますか?私は解決策を長い間探して、それらのすべてを試してみました(シュワルツカウンターソリューションを含む)が、実際に機能するものがあるのではないかと疑い始めました。
1つの可能性は、静的関数メンバーのトリックです。
Type& globalObject()
{
static Type theOneAndOnlyInstance;
return theOneAndOnlyInstance;
}
実際、これは機能します。残念ながら、globalObject.MemberFunction()の代わりにglobalObject()。MemberFunction()を作成する必要があります。その結果、やや混乱したエレガントなクライアントコードが作成されます。
更新: あなたの反応をありがとう。残念ながら、私は自分の質問に答えたようです。私はそれと一緒に暮らすことを学ぶ必要があると思います...
あなたはあなた自身の質問に答えました。静的な初期化の順序は未定義であり、静的な初期化を実行しながら(つまり、完全にリファクタリングしないように)最も洗練された方法は、関数で初期化をラップすることです。
C++を読むFAQで始まる項目 https://isocpp.org/wiki/faq/ctors#static-init-order
たぶん、あなたは非常に多くのグローバルな静的変数が必要かどうかを再考する必要があります。特に有用な場合もありますが、多くの場合、特に一部の静的変数が他の静的変数に依存していることがわかった場合は、より小さなローカルスコープにリファクタリングする方がはるかに簡単です。
しかし、あなたは正しい、初期化の特定の順序を保証する方法はないので、あなたの心がそれに設定されている場合、あなたが言及したように、関数で初期化を維持することはおそらく最も簡単な方法です。
実際、これは機能します。残念ながら、globalObject.MemberFunction()の代わりにglobalObject()。MemberFunction()を作成する必要があります。その結果、やや混乱したエレガントなクライアントコードが作成されます。
しかし、最も重要なことは、それが機能すること、そしてそれが失敗の証拠であるということです。正しい使用法をバイパスするのは簡単ではありません。
プログラムの正確性が最優先事項です。また、私見、上記の()は純粋に文体的です-すなわち。完全に重要ではありません。
プラットフォームによっては、動的初期化が多すぎることに注意してください。動的初期化子に対して実行できるクリーンアップは比較的少量です( here を参照)。この問題は、異なるグローバルオブジェクトのメンバーを含むグローバルオブジェクトコンテナを使用して解決できます。したがって、次のものがあります。
Globals & getGlobals ()
{
static Globals cache;
return cache;
}
プログラム内のすべてのグローバルオブジェクトをクリーンアップするために、〜Globals()の呼び出しは1つだけです。グローバルにアクセスするには、次のようなものがまだあります。
getGlobals().configuration.memberFunction ();
本当に必要な場合は、これをマクロでラップして、マクロを使用して入力をわずかに節約できます。
#define GLOBAL(X) getGlobals().#X
GLOBAL(object).memberFunction ();
ただし、これは最初のソリューションでは単なる構文上の砂糖です。
このスレッドの年齢に反して、私が見つけた解決策を提案したいと思います。私の前に多くの人が指摘したように、C++は静的な初期化の順序付けのメカニズムを提供していません。私が提案するのは、各静的メンバーをクラスの静的メソッド内にカプセル化することです。静的メソッドは、メンバーを初期化し、オブジェクト指向の方法でアクセスを提供します。他のメンバーの中でも「PI」を含む「Math」という名前のクラスを定義すると仮定して、例を挙げましょう。
class Math {
public:
static const float Pi() {
static const float s_PI = 3.14f;
return s_PI;
}
}
s_PIは、Pi()メソッドが(GCCで)初めて呼び出されたときに初期化されます。注意:静的ストレージを持つローカルオブジェクトには、実装依存のライフサイクルがあります。詳細については、 2 の6.7.4を確認してください。
ほとんどのコンパイラ(リンカー)は、実際には順序を指定する(移植性のない)方法をサポートしています。たとえば、Visual Studioでは、 init_seg プラグマを使用して、初期化をいくつかの異なるグループに配置できます。私の知る限り、各グループ内で順序を保証する方法はありません。これは移植性がないため、デザインを修正して必要としないかどうかを検討することもできますが、オプションはあります。
メソッドで静的をラップすると順序の問題は解決しますが、他の人が指摘したようにスレッドセーフではありませんが、懸念がある場合はスレッドにすることもできます。
// File scope static pointer is thread safe and is initialized first.
static Type * theOneAndOnlyInstance = 0;
Type& globalObject()
{
if(theOneAndOnlyInstance == 0)
{
// Put mutex lock here for thread safety
theOneAndOnlyInstance = new Type();
}
return *theOneAndOnlyInstance;
}