静的変数の代わりに使用するもの
-
26-10-2019 - |
質問
C ++プログラムでは、できればプログラムが開始されたときに1回インスタンス化されるヘルパー定数オブジェクトが必要です。これらのオブジェクトはほとんど同じ翻訳ユニット内で使用されるため、これを行う最も簡単な方法は、それらを静的にすることです。
static const Helper h(params);
しかし、それからこれがあります 静的初期化順序 問題、だから Helper
他のいくつかの統計を指します(経由 params
)、これはUBにつながる可能性があります。
別のポイントは、最終的にこのオブジェクトを複数のユニット間で共有する必要があるかもしれないということです。私がそれを残すなら static
.hファイルに入れて、複数のオブジェクトにつながります。気にすることでそれを避けることができました extern
など、しかし、これは最終的に同じ初期化順序の問題を引き起こす可能性があります(そして、それが非常にc-ishに見えるとは言っていません)。
私はシングルトンについて考えましたが、それはボイラープレートコードと不便な構文のために過剰になります(例: MySingleton::GetInstance().MyVar
) - これらのオブジェクトはヘルパーであるため、それらを複雑にするためではなく、物事を単純化することになっています...
同じC ++ FAQ 言及 このオプション:
Fred& x()
{
static Fred* ans = new Fred();
return *ans;
}
これは本当に使用され、良いことと考えられていますか?このようにする必要がありますか、それとも他の選択肢を提案しますか?ありがとう。
編集:実際にそのヘルパーが必要な理由を明確にする必要がありました。それらは通常の定数に非常に似ており、事前に計算された可能性がありますが、実行時にそれを行う方が便利です。マルチスレッドの問題を自動的に解決するため、メインの前にインスタンス化することをお勧めします(C ++ 03ではローカルスタティックが保護されていません)。また、私が言ったように、それらはしばしば翻訳ユニットに限定されるので、それらをエクスポートしてmain()で初期化することは意味がありません。それらは単なる定数と考えることができますが、実行時にしか知られていません。
解決
グローバルな状態にはいくつかの可能性があります(可変であるかどうかにかかわらず)。
初期化の問題があることを恐れている場合は、 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);
別のデザインはやや似ていますが、非グローバルデザインへの移行がはるかに簡単になるため、私はそれを好みます。
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
ルールとは、動作を指定し、コンパイラ/リンカー/ランタイムに任せて、それが提供されている方法については世界に注意することなく、それを提供することを意味します。これが実際に最適化を可能にするものです。
したがって、コンパイラチェーンが定数のアドレスが取られないと推測できる場合、定数を完全に排除する可能性があります。いくつかの定数が常に等しく、再び彼らのアドレスが検査されないことを推測できる場合、それはそれらをマージする可能性があります。
他のヒント
はい、使用できます 最初の使用時に構築します それがあなたの問題を簡素化するならば、イディオム。これは 常に良い 初期化のグローバルオブジェクトよりも それによる 他のグローバルオブジェクトについて。
もう1つの選択肢はです シングルトンパターン. 。どちらも同様の問題を解決できます。しかし、どの状況に適しているかを決定し、要件を満たす必要があります。
私の知る限り、これらの2つのアプローチほど「より良い」ものはありません。
シングルトンとグローバルオブジェクトはしばしば悪と見なされます。最も簡単で最も柔軟な方法は、あなたのオブジェクトをインスタンス化することです main
このオブジェクトを機能し、他の関数に渡します。
void doSomething(const Helper& h);
int main() {
const Parameters params(...);
const Helper h(params);
doSomething(h);
}
別の方法は、ヘルパーが非会員に機能するようにすることです。たぶん、彼らはまったく状態を必要としないかもしれません、そして彼らがそうするならば、あなたはそれらを呼び出すときにステートフルオブジェクトを渡すことができます。
FAQで言及されている局所的な静的イディオムに対して何も話さないと思います。それはシンプルで、スレッドセーフである必要があり、オブジェクトが変化しない場合は、簡単にock笑し、遠くにアクションを導入できないはずです。
します Helper
前に存在する必要があります main
実行?そうでない場合は、(?のセット)グローバルを作成します ポインター変数 初期化 0
. 。次に、メインを使用して、それらを決定的な順序で定期状態に入力します。あなたが好きなら、あなたのために控えめなヘルパー機能を作成することもできます。