web-dev-qa-db-ja.com

C ++ NiftyCounterイディオム。どうして?

私は最近、 Nifty Counter Idiom に出くわしました。私の理解では、これはcout、cerrなどの標準ライブラリにグローバルを実装するために使用されます。専門家が選択したので、非常に強力な手法だと思います。

MeyerSingletonのようなものを使用することの利点が何であるかを理解しようとしています。

たとえば、ヘッダーファイルに次のように含めることができます。

inline Stream& getStream() { static Stream s; return s; }
static Stream& stream = getStream();

利点は、参照カウント、新しい配置、または2つのクラスを持つことを心配する必要がないことです。つまり、コードがはるかに単純です。このように行われていないので、私は理由があると確信しています:

  1. これは、共有ライブラリと静的ライブラリにまたがる単一のグローバルオブジェクトを持つことが保証されていませんか? ODRは、静的変数が1つだけ存在できることを保証する必要があるようです。
  2. パフォーマンスコストはありますか?私のコードとNiftyCounterの両方で、オブジェクトに到達するために1つの参照に従っているようです。
  3. 参照カウントが実際に役立つ状況はありますか? Meyer Singletonのように、ヘッダーが含まれていれば、オブジェクトが構築され、プログラムの最後に破棄されるだけのようです。
  4. 答えは手動で何かをdlopenすることを含みますか?私はそれについてあまり経験がありません。

編集:Yakkの回答を読んでいるときに、次のコードを書くように求められました。簡単なデモとして元の質問に追加します。これは、Meyer Singleton +グローバル参照を使用すると、メインの前に初期化される方法を示す非常に最小限の例です: http://coliru.stacked-crooked.com/a/a7f0c8f33ba42b7f

29
Nir Friedman

静的ローカル/マイヤーのシングルトン+静的グローバル参照(ソリューション)は、気の利いたカウンターとほぼ同等です。

違いは次のとおりです。

  1. ソリューションに.cppファイルは必要ありません。

  2. 技術的には、_static Steam&_はすべてのコンパイルユニットに存在します。参照されているオブジェクトはそうではありません。現在のバージョンのC++ではこれを検出する方法がないため、あたかもこれがなくなるかのように。ただし、一部の実装では、参照を削除するのではなく、実際に作成する場合があります。

  3. _static Stream&_が作成される前に、誰かがgetStream()を呼び出す可能性があります。これにより、破棄順序が困難になります(ストリームが予想よりも遅く破棄されます)。これは、ルールに反することで回避できます。

  4. この標準は、inlinegetStreamスレッドでの_static Stream_ローカルの作成を安全にするために義務付けられています。これが起こらないことを検出することはコンパイラーにとって困難であるため、ソリューションに冗長なスレッドセーフオーバーヘッドが存在する可能性があります。気の利いたカウンターはnotスレッドセーフを明示的にサポートします。これは、スレッドが予期される前の静的初期化時に実行されるため、安全であると見なされます。

  5. getStream()の呼び出しは、すべてのコンパイルユニットで発生する必要があります。何もできないことが証明された場合にのみ、最適化することができますが、これは困難です。気の利いたカウンターにも同様のコストがかかりますが、操作は最適化するのが簡単な場合とそうでない場合があります。 (これを決定するには、さまざまなコンパイラで結果のアセンブリ出力を検査する必要があります)

  6. C++ 11で導入された「マジックスタティック」(競合状態のないスタティックローカル)。コードでC++ 11マジックスタティックが実行される前に、他の問題が発生する可能性があります。私が考えることができる唯一のものは、静的初期化中に別のスレッドで直接getStream()を呼び出す人です。これは(上記のように)一般的に禁止されるべきです。

  7. 標準の領域外では、バージョンは動的にリンクされたコードのチャンク(DLL、.soなど)ごとに新しいシングルトンを自動的かつ魔法のように作成します。気の利いたカウンターは、cppファイルにシングルトンのみを作成します。これにより、ライブラリの作成者は、誤って新しいシングルトンを生成することをより厳密に制御できるようになります。複製を生成する代わりに、ダイナミックライブラリに貼り付けることができます。

複数のシングルトンを持つことを避けることが重要な場合があります。

回答とコメントの要約:

グローバルシングルトンを変数として、またはゲッター関数を介して提示したい、ライブラリの3つの異なるオプションを比較してみましょう。

オプション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"; }
3
Amir Kirsh

ここにあるソリューションでは、グローバル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;
}
0
mpm

Nifty Counter(別名Schwartz Counter)のユーティリティ/パフォーマンスに関するすべての質問は、基本的にこの回答でMaxim Egorushkinによって回答されました(ただし、コメントスレッドも参照してください)。

最新のC++のグローバル変数

主な問題は、トレードオフが発生していることです。 Nifty Counterを使用すると、プログラムの起動時間が少し遅くなります(大規模なプロジェクトでは)。これらのカウンターはすべて、何かが発生する前に実行する必要があるためです。それはマイヤーのシングルトンでは起こりません。

ただし、Meyerのシングルトンでは、グローバルオブジェクトにアクセスするたびに、それがnullかどうかを確認する必要があります。そうでない場合、コンパイラは、アクセスが試行される前に静的変数が既に構築されているかどうかを確認するコードを発行します。ニフティカウンターでは、起動時にinitが発生したと想定できるため、既にポインターがあり、起動するだけです。

したがって、Nifty CounterとMeyerのシングルトンは、基本的にプログラムの起動時間と実行時間の間のトレードオフです。

0
Chris Beck