3

C++ プログラムでは、できればプログラムの開始時に一度インスタンス化されるヘルパー定数オブジェクトが必要です。これらのオブジェクトはほとんど同じ翻訳単位内で使用されるため、これを行う最も簡単な方法はそれらを静的にすることです。

static const Helper h(params);

しかし、この静的初期化順序の問題があるため、Helper( を介して) 他の静的を参照するとparams、UB につながる可能性があります。

もう 1 つのポイントは、最終的にはこのオブジェクトを複数のユニットで共有する必要があるということです。そのままにstaticして.hファイルに入れると、複数のオブジェクトが作成されます。などを気にすることでそれを回避できましexternたが、これにより最終的に同じ初期化順序の問題が発生する可能性があります(非常にCっぽいとは言えません)。

私はシングルトンについて考えましたが、定型コードと不便な構文 (例: MySingleton::GetInstance().MyVar) のためにやり過ぎです - これらのオブジェクトはヘルパーであるため、物事を複雑にするのではなく単純化する必要があります...

同じ C++ FAQでこのオプションについて言及されています。

 Fred& x()
 {
   static Fred* ans = new Fred();
   return *ans;
 } 

これは本当に使用され、良いことだと考えられていますか? このようにする必要がありますか、それとも他の方法をお勧めしますか? ありがとう。

編集:ヘルパーが実際に必要な理由を明確にする必要がありました。それらは通常の定数に非常に似ており、事前に計算することもできましたが、実行時にそれを行う方が便利です。マルチスレッドの問題を自動的に解決するため、メインの前にそれらをインスタンス化することをお勧めします (ローカルの静的は C++03 では保護されていません)。また、私が言ったように、それらはしばしば翻訳単位に限定されるため、それらをエクスポートして main() で初期化することは意味がありません。それらは単なる定数と考えることができますが、実行時にのみ認識されます。

4

4 に答える 4

5

グローバル状態にはいくつかの可能性があります (変更可能かどうかに関係なく)。

初期化の問題が発生する恐れがある場合は、このlocal staticアプローチを使用してインスタンスを作成する必要があります。

あなたが提示する不格好なシングルトン設計は必須の設計ではないことに注意してください。

class Singleton
{
public:
  static void DoSomething(int i)
  {
    Singleton& s = Instance();
    // do something with i
  }


private:
  Singleton() {}
  ~Singleton() {}

  static Singleton& Instance()
  {
    static Singleton S; // no dynamic allocation, it's unnecessary
    return S;
  }
};

// Invocation
Singleton::DoSomething(i);

もう 1 つのデザインは多少似ていますが、非グローバル デザインへの移行がはるかに簡単になるため、私はそれを好みます。

class Monoid
{
public:
  Monoid()
  {
    static State S;
    state = &s;
  }

  void doSomething(int i)
  {
    state->count += i;
  }

private:
  struct State
  {
    int count;
  };

  State* state;
};


// Use
Monoid m;
m.doSomething(1);

ここでの正味の利点は、状態の「グローバル性」が隠されていることです。これは、クライアントが心配する必要のない実装の詳細です。キャッシュに非常に便利です。

デザインについて質問させてください。

  • 実際に特異点を強制する必要がありますか?
  • 実際に開始前にオブジェクトを構築する必要がありますmainか?

特異点は一般的に過度に強調されています。ここでは C++0x が役に立ちますが、それでも、プログラマーの振る舞いに依存するのではなく、技術的に特異点を強制するのは非常に煩わしい場合があります。それぞれの間で構成を変更するだけですか?うーん。一度インスタンス化して、仲間のプログラマーを信頼する方がはるかに簡単です...または機能テスト;)

2 番目の質問は、機能よりも技術的なものです。プログラムのエントリ ポイントの前に構成が必要な場合は、開始時に単純に読み取ることができます。

素朴に聞こえるかもしれませんが、実際には、ライブラリのロード中の計算には 1 つの問題があります。それは、エラーをどのように処理するかということです。スローすると、ライブラリはロードされません。投げ続けないと無効状態。面白くないですよね?通常の制御フロー ロジックを使用できるため、実際の作業が開始されると、作業ははるかに単純になります。

そして、状態が有効かどうかをテストすることを考えているなら...なぜ、テストしたいポイントですべてを構築しないのですか?

最後に、非常に問題とglobalなるのは、導入される隠れた依存関係です。実行の流れやリファクタリングの影響を推論するために依存関係が暗黙的に示されている場合は、はるかに優れています。


編集

初期化順序の問題について: 単一の翻訳単位内のオブジェクトは、定義された順序で初期化されることが保証されています。

したがって、次のコードは標準に従って有効です。

static int foo() { return std::numeric_limits<int>::max() / 2; }
static int bar(int c) { return c*2; }

static int const x = foo();
static int const y = bar(x);

初期化順序は、別の翻訳単位で定義された定数/変数を参照する場合にのみ問題になります。そのため、同じ翻訳単位内のオブジェクトstaticのみを参照する限り、オブジェクトは問題なく自然に表現できます。static

スペースの問題に関して:as-ifルールはここで驚異的な効果を発揮します。非公式に言えば、このas-ifルールは、動作を指定し、それがどのように提供されるかについて世界で気にせずに、コンパイラ/リンカー/ランタイムに任せて提供することを意味します。これにより、実際に最適化が可能になります。

したがって、コンパイラ チェーンが定数のアドレスが取得されないことを推論できる場合、定数を完全に除外する可能性があります。いくつかの定数が常に等しいと推測でき、それらのアドレスが決して検査されないことが再び推測できる場合、それらをマージすることができます。

于 2011-01-28T15:55:35.120 に答える
2

はい、問題を単純化する場合は、 Construct On First Use Idiom を使用できます。初期化が他のグローバル オブジェクトに依存するグローバル オブジェクトよりも優れています。

もう 1 つの選択肢はSingleton Patternです。どちらも同様の問題を解決できます。ただし、どちらが状況により適しているかを判断し、要件を満たす必要があります。

私の知る限り、これら 2 つのアプローチよりも「優れた」ものはありません。

于 2011-01-28T15:14:32.260 に答える
0

実行Helper前に存在する必要がありますか? mainそうでない場合は、(一連の?) グローバルポインター変数を に初期化し0ます。次に、main を使用して、一定の状態を決定的な順序で入力します。必要に応じて、逆参照を行うヘルパー関数を作成することもできます。

于 2011-01-28T16:03:41.043 に答える
0

シングルトンとグローバル オブジェクトはしばしば悪と見なされます。最も簡単で柔軟な方法は、関数内でオブジェクトをインスタンス化し、mainこのオブジェクトを他の関数に渡すことです。

void doSomething(const Helper& h);
int main() {
  const Parameters params(...);
  const Helper h(params);
  doSomething(h);
}

もう 1 つの方法は、ヘルパー関数を非メンバーにすることです。状態がまったく必要ない場合もあります。必要な場合は、呼び出すときにステートフル オブジェクトを渡すことができます。

FAQ に記載されているローカルの静的イディオムに反対するものは何もないと思います。シンプルで、スレッドセーフであるべきです。また、オブジェクトがミュータブルでない場合は、簡単にモック可能で、離れた場所でアクションを導入しない必要があります。

于 2011-01-28T15:02:06.220 に答える