web-dev-qa-db-ja.com

C ++シングルトンと完全に静的なオブジェクト

プロジェクトにあるクラスのインスタンスを1つだけ持つ必要があるとしましょう。それを行うにはいくつかの方法があります。

比較したいです。私の理解を確認してください。

1)クラシックシングルトンパターン

2)完全に静的なクラス(すべてのメソッドとメンバーは静的です)。


私が理解しているように、違いは次のとおりです。

a)異なるユニット間での静的メンバーの初期化の順序は定義されていません。したがって、完全に静的なメンバーの初期化では、他のモジュールの静的なメンバー/関数を使用できません。また、シングルトンにはこの問題はありません。

b)シングルトンのgetInstance()のスレッディングを処理する必要があります。ただし、完全に静的なクラスにはこの問題はありません。

c)メソッドへのアクセスは少し異なります。 Foo :: bar(); vs Foo :: getInstance()-> bar();一般に、シングルトンはNULLを返して、オブジェクトの構築に問題があったことを識別できますが、静的クラスはできません。

d)クラスの定義は、静的クラスの静的データの束で少し不格好に見えます。

何か逃したことがありますか?

35
Victor Ronin

それをシングルトンと呼ぶか、モノステートと呼ぶか、または派手な名前と呼ぶか、これの非常に厄介な性質は、オブジェクトのインスタンスが1つあり、それに多数の書き込みを行うということです。グローバル変数は、その名前が何であれ、です。

ユニークなインスタンスを必要とする)と言う考えは、一般的に不格好です。ほとんどの場合、実際に必要なのは、同じインスタンスを共有するパーツです。しかし、別のパーツグループが別のインスタンスを問題なく完全に使用できます。

needグローバル変数を要求するすべてのコードは非常に疑わしいです。それを使用する方が簡単に見えるかもしれませんが、それに直面しましょう。すべての関数にオブジェクトを完全に渡すことができ、そのシグネチャは複雑になりますが、それでも働きます。

ただし、問題に気付くまでは、グローバル変数を使用する方が簡単に見えます。

  • マルチスレッドが危険にさらされている
  • testabilityは減少します。1つのテストが後続のテストに影響を与える可能性があるためです
  • 依存関係の分析は非常に複雑です。サブメソッド内からグローバルをプルすると、メソッドがどの状態に依存するかを知るのは困難です...

シングルトンに関する限り、マルチスレッドの作成はC++ 0xより前のC++では使用できません(静的ローカルを使用して可能になる場合)。したがって、1つのスレッドでのみ作成し、アクセスを遅らせる必要があります。メインでインスタンス化します。 、それはあなたの最善の策です。

シングルトン/スタティックの寿命は、他の人がそれを実行する前に終了する可能性があるため、破壊によって騒乱が発生する可能性があります。これは、Loggerシングルトンの典型です。通常の戦略は恥知らずにリークすることです...

その後、まだそれが必要な場合は、幸運を祈ってください。このコミュニティでできることはそれだけです。

21
Matthieu M.

あなたが見落としているもう1つのオプションはnamespaceです。

namespace xyz {
namespace {
    int private_variable;
}

int get_pv() {
    return private_variable;
}
}

機能的には、これはオプション#2と似ていますが、このことを誤って「削除」することはできません。誤ってインスタンスを作成することはできません。これは、関連するグローバルにアクセス可能なデータと機能の単なるコレクションです。 (私の例のように) "プライベート"なメンバーと関数を持つこともできます。

もちろん、使用法は次のようになります。

int x = xyz::get_pv();
9
Evan Teran

プロジェクトにあるクラスのインスタンスを1つだけ持つ必要があるとしましょう。それを行うにはいくつかの方法があります。

より良いソリューション:

必要なすべての関数にパラメーターとして渡すmainの変数は、別のものになります。

a)異なるユニット間での静的メンバーの初期化の順序は定義されていません。したがって、完全に静的なメンバーの初期化では、他のモジュールの静的なメンバー/関数を使用できません。また、シングルトンにはこの問題はありません。

シングルトンは、コンストラクタ/デストラクタが他のグローバル静的寿命変数にアクセスする場合にこの問題を抱えています。

b)SigletonのgetInstance()のスレッド化に対処する必要があります。ただし、完全に静的なクラスにはこの問題はありません。

それは本当に問題ではありませんか?あなたがそれを知っているなら、コードに適切なロックを追加するだけです。

c)メソッドへのアクセスは少し異なります。 Foo :: bar(); vs Foo :: getInstance()-> bar();一般に、sigletonはNULLを返して、オブジェクトの構築にいくつかの問題があり、静的クラスはそれができないことを識別できます。

GetInstance()が参照を返すようにします。そうすれば、ポインタがNULLであっても曖昧さはなくなります。機能するか、例外が発生しました。また、これはインスタンスの破壊が正しく呼び出される設計につながります(これをシングルトンを使用するためのアドバイスとして使用しないでください(可能であれば回避します(ただし、使用する場合はきちんとします))。

d)クラスの定義は、静的クラスの静的データの束で少し不格好に見えます。

シングルトンを適切に書くよりも不格好です。

これらの両方のメソッドの問題は、両方がglobal mutable stateにアクセスしているため、他のオブジェクトによるこれらの「単一インスタンス」オブジェクトの使用がユーザーから隠されていることです。これにより、テストで問題が発生する可能性があります(TDDには外部機能をモックする機能が必要ですが、global mutable stateはテスターが外部依存関係を(簡単に)モックする機能を妨げます)。

非PODのオブジェクトには、例外をスローする可能性のあるコンストラクターがあります。したがって、グローバル名前空間のオブジェクトの場合、これは、main()に入る前に例外がスローされる可能性があることを意味します(これにより、バグを見つけるのが難しくなる可能性があります(グローバルオブジェクトがたくさんある場合(どこにでもブレークポイントを置く必要があります))。遅延評価されるシングルトンにも同じ問題が存在します。最初の使用時にスローすると、これを修正して後続の試行がスローされないようにしますか?シングルトンが取得されるたびにアプリケーションがスローし続けるのですか?

1
Martin York

共有ポインタークラスにアクセスできない限り、C++では少し複雑なBorgパターンを使用することもできます。アイデアは、任意の数のBorgクラスをインスタンス化できますが、それらの状態はすべてのインスタンスで共有されるということです。

1
Colin

静的オブジェクトは例外をスローする可能性があります。実行可能ファイルが起動せず、デバッグ/処理が困難です。

0
Jay