私は最近、 Nifty Counter Idiom に出くわしました。私の理解では、これはcout、cerrなどの標準ライブラリにグローバルを実装するために使用されます。専門家が選択したので、非常に強力な手法だと思います。
MeyerSingletonのようなものを使用することの利点が何であるかを理解しようとしています。
たとえば、ヘッダーファイルに次のように含めることができます。
inline Stream& getStream() { static Stream s; return s; }
static Stream& stream = getStream();
利点は、参照カウント、新しい配置、または2つのクラスを持つことを心配する必要がないことです。つまり、コードがはるかに単純です。このように行われていないので、私は理由があると確信しています:
編集:Yakkの回答を読んでいるときに、次のコードを書くように求められました。簡単なデモとして元の質問に追加します。これは、Meyer Singleton +グローバル参照を使用すると、メインの前に初期化される方法を示す非常に最小限の例です: http://coliru.stacked-crooked.com/a/a7f0c8f33ba42b7f 。
静的ローカル/マイヤーのシングルトン+静的グローバル参照(ソリューション)は、気の利いたカウンターとほぼ同等です。
違いは次のとおりです。
ソリューションに.cppファイルは必要ありません。
技術的には、_static Steam&
_はすべてのコンパイルユニットに存在します。参照されているオブジェクトはそうではありません。現在のバージョンのC++ではこれを検出する方法がないため、あたかもこれがなくなるかのように。ただし、一部の実装では、参照を削除するのではなく、実際に作成する場合があります。
_static Stream&
_が作成される前に、誰かがgetStream()
を呼び出す可能性があります。これにより、破棄順序が困難になります(ストリームが予想よりも遅く破棄されます)。これは、ルールに反することで回避できます。
この標準は、inline
getStream
スレッドでの_static Stream
_ローカルの作成を安全にするために義務付けられています。これが起こらないことを検出することはコンパイラーにとって困難であるため、ソリューションに冗長なスレッドセーフオーバーヘッドが存在する可能性があります。気の利いたカウンターはnotスレッドセーフを明示的にサポートします。これは、スレッドが予期される前の静的初期化時に実行されるため、安全であると見なされます。
getStream()
の呼び出しは、すべてのコンパイルユニットで発生する必要があります。何もできないことが証明された場合にのみ、最適化することができますが、これは困難です。気の利いたカウンターにも同様のコストがかかりますが、操作は最適化するのが簡単な場合とそうでない場合があります。 (これを決定するには、さまざまなコンパイラで結果のアセンブリ出力を検査する必要があります)
C++ 11で導入された「マジックスタティック」(競合状態のないスタティックローカル)。コードでC++ 11マジックスタティックが実行される前に、他の問題が発生する可能性があります。私が考えることができる唯一のものは、静的初期化中に別のスレッドで直接getStream()
を呼び出す人です。これは(上記のように)一般的に禁止されるべきです。
標準の領域外では、バージョンは動的にリンクされたコードのチャンク(DLL、.soなど)ごとに新しいシングルトンを自動的かつ魔法のように作成します。気の利いたカウンターは、cppファイルにシングルトンのみを作成します。これにより、ライブラリの作成者は、誤って新しいシングルトンを生成することをより厳密に制御できるようになります。複製を生成する代わりに、ダイナミックライブラリに貼り付けることができます。
複数のシングルトンを持つことを避けることが重要な場合があります。
回答とコメントの要約:
オプション1気の利いたカウンターパターン 、グローバル変数つまり:
オプション2参照変数を使用したマイヤーズシングルトンパターン(質問に示されているとおり):
ただし、すべての共有オブジェクトとメインがライブラリに動的にリンクされている場合でも、共有オブジェクトにシングルトンオブジェクトのコピーが作成されます。これは、シングルトン参照変数がヘッダーファイルで静的に宣言されており、コンパイル時に、共有オブジェクトを含め、コンパイル時に、ロードされるプログラムに出会う前に、初期化の準備ができている必要があるためです。
オプション --- 参照変数のないマイヤーズシングルトンパターン(シングルトンオブジェクトを取得するためのゲッターの呼び出し):
ただし、このオプションにはグローバル変数もインライン呼び出しもありません。シングルトンを取得するための各呼び出しは関数呼び出しです(呼び出し側でキャッシュできます)。
このオプションは次のようになります。
// libA .h
struct A {
A();
};
A& getA();
// some other header
A global_a2 = getA();
// main
int main() {
std::cerr << "main\n";
}
// libA .cpp - need to be dynamically linked! (same as libstdc++ is...)
// thus the below shall be created only once in the process
A& getA() {
static A a;
return a;
}
A::A() { std::cerr << "construct A\n"; }
ここにあるソリューションでは、グローバルstream
変数は静的初期化中のある時点で割り当てられますが、いつ指定されていません。したがって、静的初期化中に他のコンパイルユニットからのstream
を使用すると機能しない場合があります。気の利いたカウンターは、静的な初期化中でもグローバル(std :: coutなど)が使用可能であることを保証する方法です。
#include <iostream>
struct use_std_out_in_ctor
{
use_std_out_in_ctor()
{
// std::cout guaranteed to be initialized even if this
// ctor runs during static initialization
std::cout << "Hello world" << std::endl;
}
};
use_std_out_in_ctor global; // causes ctor to run during static initialization
int main()
{
std::cout << "Did it print Hello world?" << std::endl;
}
Nifty Counter(別名Schwartz Counter)のユーティリティ/パフォーマンスに関するすべての質問は、基本的にこの回答でMaxim Egorushkinによって回答されました(ただし、コメントスレッドも参照してください)。
主な問題は、トレードオフが発生していることです。 Nifty Counterを使用すると、プログラムの起動時間が少し遅くなります(大規模なプロジェクトでは)。これらのカウンターはすべて、何かが発生する前に実行する必要があるためです。それはマイヤーのシングルトンでは起こりません。
ただし、Meyerのシングルトンでは、グローバルオブジェクトにアクセスするたびに、それがnullかどうかを確認する必要があります。そうでない場合、コンパイラは、アクセスが試行される前に静的変数が既に構築されているかどうかを確認するコードを発行します。ニフティカウンターでは、起動時にinitが発生したと想定できるため、既にポインターがあり、起動するだけです。
したがって、Nifty CounterとMeyerのシングルトンは、基本的にプログラムの起動時間と実行時間の間のトレードオフです。