51

私の友人は今日、なぜグローバル静的オブジェクトよりシングルトンの使用を好むのか私に尋ねました。 私が説明し始めた方法は、シングルトンが状態を持つことができるということでしたが、静的グローバルオブジェクトはそうではありませんでした...しかし、私は確信が持てませんでした。

他に比べて利点は何ですか? (C ++)

8 답변


56

実際には、C ++では推奨される方法はローカル静的オブジェクトです。

Printer & thePrinter() {
    static Printer printer;
    return printer;
}

これは技術的にはシングルトンですが、この関数はクラスの静的メソッドでさえあり得ます。そのため、グローバルな静的オブジェクトとは異なり、使用する前に構築することを保証します。これは、任意の順序で作成できるため、あるグローバルオブジェクトが別のグローバルオブジェクトを使用すると矛盾なく失敗する可能性があります。

呼び出しによって新しいインスタンスを作成してシングルトンを作成する一般的な方法より優れている点newつまり、オブジェクトデストラクタはプログラムの最後に呼び出されます。動的に割り当てられたシングルトンでは起こりません。

他の良い方法は、他の静的メソッドやサブクラスからでさえも、singletonにアクセスする方法がないことです。デバッグ時間を節約します。


  • これはシングルトンを作成するための最良の方法です。インターネット上にたくさんの例がありますが、newを使用することは実際のコードでは一般的ではありません(それらを信じてはいけません)。 - Martin York
  • 実際には、「ベスト」はありません。シングルトンの作り方このシングルトンがデストラクタ内の別のシングルトンを参照している場合は、正常に動作しません。 - rlbond
  • これは一般にスレッドセーフではないことに注意してください(実装に依存します:例えばg ++はロックを挿入しますが、MSVCはそうしません)。 - Pavel Minaev
  • もしあなたのシングルトンが他のシングルトンを参照するのであれば、あなたはこれまでに別のコード行を書くことを禁じられるべきです。そして確かにもっとシングルトンを書くことから。その後、すべてのデータがグローバルでなければならないという1970年の考え方に固執します。 - jalf
  • ...そしてあなたはそれを次のようにすることができますPrinterコンストラクタを非公開にして作成することによって、他の誰かによってインスタンス化されることはありませんthePrinter()友達機能。 - Catskul

22

C ++では、異なるコンパイル単位における静的オブジェクトのインスタンス化の順序は未定義です。したがって、あるグローバルが、構築されていない別のグローバルを参照して、プログラムを破壊する可能性があります。シングルトンパターンは、静的メンバー関数または自由関数に構造を結び付けることによってこの問題を解決します。

まともな要約がありますここに


  • 次に、2つのスレッドが同時にその関数を呼び出すことがあるため、複雑な競合条件を導入してスレッドの安全性を捨てます。 - jalf
  • おおまかに言って、std :: coutは、普通のグローバルオブジェクトであるにもかかわらず、うまく動くように思えます…初期化の順番がこんなに大きな問題だったのであれば、標準委員会はもう気付いていたでしょう。グローバルを使いすぎて、それらを互いに依存させている場合にのみ問題になります。 - jalf
  • @ jalf:宿題をしなさい。coutそしてcin前に構築するように特に指定されているmain()標準による。他のグローバルオブジェクトはこの扱いを受けません。さらに、スレッドセーフなシングルトン実装が存在し、自分で書くのは難しくありません。二重施工を防ぐために必要なのは二重ロックだけです。個人的に私はシングルトンを避けようとしています、私はパターンが使いすぎだと思います。しかし、あなたがC ++の知識を欠いているからといって、私を軽蔑するわけにはいきません。 - rlbond
  • あなたの二重ロックパターンはありませんではないこの場合は動作します。静的オブジェクトは確実に初期化されます。同じ翻訳単位内の関数が呼び出される前これには、オブジェクトを初期化したいものも含まれます。つまり、ロックが入力される前にオブジェクトが初期化されるため、ロックはあまり使用されません。 :) - jalf
  • @rlbond:cinとcoutに関して特別なことは何もありません。それらは他のグローバル変数とまったく同じように扱われます。正しく初期化されていることを確認するために使用できるトリックは他にもあります。 - Martin York

8

私の友人は今日、なぜグローバル静的オブジェクトよりシングルトンの使用を好むのか私に尋ねました。私が説明し始める方法は、シングルトンが状態を持つことができるということでした - 静的なグローバルオブジェクトはそうではありませんでした...しかしそれから私は確かではありませんでした。

静的グローバルオブジェクトは、C#でも状態を持つことができます。

class myclass {
 // can have state
 // ...
 public static myclass m = new myclass(); // globally accessible static instance, which can have state
}

他に比べて利点は何ですか? (C ++)

シングルトンはあなたのコードを不自由にしますが、グローバルな静的インスタンスはそうしません。 シングルトンに関する問題については、SOに無数の質問があります。これがひとつそしてもう一つまたは別の

一言で言えば、シングルトンはあなたに2つのことを与えます:

  • グローバルにアクセス可能なオブジェクト
  • 1つのインスタンスしか作成できないという保証。

最初のポイントだけが欲しい場合は、グローバルにアクセス可能なオブジェクトを作成する必要があります。 そしてなぜ私たちは二度目が欲しいですか?私たちはしません知っている将来どのようにコードが使用されるのかを事前に判断します。では、なぜそれを特定して有用な機能を削除するのでしょうか。私たちはいつも違う「必要なインスタンスは1つだけになる」と予測したときそして、「1つのインスタンスだけが必要になる」との間には大きな違いがあります(正しい答えはつくる「複数のインスタンスが作成された場合、アプリケーションはどのような状況下でも正常に実行できません。クラッシュし、ユーザーのハードドライブをフォーマットし、インターネットで機密データを公開します。」アプリは壊れていますがもしそうではありません、そうです、そう、シングルトンはあなたが必要とするものです)


  • シングルトンパターンには正当な用途がいくつかあります。これは使いすぎですが、問題ない場合もあります(いくつかの例:ゲーム用のグローバルRNG。設定オプションオブジェクト、メモリプールコントローラー)。 - rlbond
  • そして私が上で述べたように、異なるコンパイル単位でのオブジェクトの構築順序は未定義です。あなたは私を信じて育てなかったcoutそしてcinしかし、これらは規格で定義されている特別な場合です。 - rlbond
  • ゲームが単一のグローバルRNGを使用する理由はほとんどありません。競合を減らしてスレッドごとに競合を減らしませんか。そして、サブシステムごとに違うものが欲しいかもしれません。マルチプレイヤーのシナリオでは、プレイヤーごとに。設定オプション2人用の使い方が簡単にわかります。アクティブになっている1組の構成と、ユーザーが現在変更しているがまだ適用されていない2組目の構成があります。してもいいです簡単に複数のメモリプールを持つ。もちろんありますありますシングルトンパターンの正当な用途しかし、それらは非常にまれであり、あなたがリストしたものはではないその中で。 - jalf
  • 実際、私はこれを書いてからあまり妥協しなくなったかもしれませんが、上記のコメントを修正したいと思い、いいえシングルトンパターンの正当な用途 - jalf
  • 私は自分のコードが将来どのように使われるのかを事前に知っています。他の使用法は現在制限されています。将来の変化は重要な議論を必要とするでしょう。だから、その場合は、シングルトンは大丈夫? - iheanyi

3

理由1:

シングルトンは作るのが簡単であるのでそれらは怠惰な造りです。

あなたはグローバルでこれを行うことができますが、それは開発者による余分な作業を要します。そのため、デフォルトでは、グローバルは常に初期化されます(名前空間を使った特別な規則は別として)。

そのため、あなたのオブジェクトが大きくて、そして/または構築するのに高価であるならば、あなたが本当にそれを使わなければならない限り、あなたはそれを構築したくないかもしれません。

理由2:

初期化(および破壊)問題の順序

GlobalRes& getGlobalRes()
{
    static GlobalRes instance;  // Lazily initialized.
    return instance;
}


GlobalResTwo& getGlobalResTwo()
{
    static GlobalResTwo instance;  // Lazy again.
    return instance;
}


// Order of destruction problem.
// The destructor of this object uses another global object so
// the order of destruction is important.
class GlobalResTwo
{
    public:
        GlobalResTwo()
        {
            getGlobalRes();
            // At this point globalRes is fully initialized.
            // Because it is fully initialized before this object it will be destroyed
            // after this object is destroyed (Guaranteed)
        }
        ~GlobalResTwo()
        {
            // It is safe to use globalRes because we know it will not be destroyed
            // before this object.
            getGlobalRes().doStuff();
        }
};


3

グローバル静的オブジェクトに対するシングルトンのもう1つの利点は、コンストラクターが非公開であるため、 "1つしか存在できない"という非常に明確な、コンパイラーによる指示があることです。

それとは対照的に、グローバルな静的オブジェクトでは、開発者がこのオブジェクトの追加のインスタンスを作成するコードを書くのを止めることは何もありません。

追加の制約の利点は、オブジェクトがどのように使用されるかに関して保証があることです。


  • "コンパイラで強制された指令"" 1つしか存在しない"これが1つのユースケースを思いつくための注意良い事?コードに恣意的かつ不必要な制約を追加しても意味がありません。あなたのコードが一般的なそして再利用可能そして、あなたが最初にすることがそれに対する制約を平手打ちすることであるなら、あなたが必要としないということは、それは勝たない。 - jalf
  • @jalf:ほとんどのコードは一般的で再利用可能なはずです。しかし、大規模なアプリケーションでは、中央の参照を必要とする複数のオブジェクト、つまりグローバルアプリケーションフレームワークを持つことができます。例としては、ある種のロックマネージャがあります。独自のロックマネージャを作成するアプリケーションのさまざまな部分は不適切です。 - Andrew Shepherd
  • いいえ、コンポーネントごとに異なるロックマネージャを簡単に用意できます。 - jalf
  • @ jalf:状況次第です。しかし、全体像を見てみると、あなたが反対していると思われるのは、クラスの使用方法に制約を課しているクラスの設計者です。これがコンポーネント設計の重要な部分であると私は主張します。それが、メソッドとフィールドをプライベートに定義できる理由です。 - Andrew Shepherd

2

シングルトン( "最初の使用時に構築")イディオムを使うと、避けることができます。静的初期化順序の失敗


  • その記事はひどいです。提案された解決策は問題と同じくらい悪いです。関数内でnewを使用することで、デストラクタを呼び出すことはなくなります。 - Martin York
  • これは、プログラムがとにかく終了しているため、メモリリークが問題にならないごくわずかな状況の1つです(メモリを取り戻すことができない本当にひどいOSを使用しているのでない限り)。次のFAQを読むと、彼はその理由を説明します。 - coppro
  • 10.12から10.16までを読んでください - aJ.
  • メモリ管理の観点からは大丈夫ですが、デストラクタが余分な作業をしているとしたらどうでしょうか。 - Michael Foukarakis
  • 私は最近、余分な作業をするためにシングルトンデストラクタが必要な問題を抱えていました。インスタンスにスマートポインタ(boost :: shared_ptr)を使用するとうまくいくことがわかった。それはしかし10.14に関連した問題に遭遇するかもしれません。 - Dan Hook

0

C ++では、実際の有用性という点で両者の間に大きな違いはありません。グローバルオブジェクトは、もちろんそれ自身の状態を維持することができます(私はそれをお勧めしませんが、おそらく他のグローバル変数で)。グローバルオブジェクトまたはシングルトンを使用する場合(および使用しない理由がたくさんあります)、グローバルオブジェクトに対してシングルトンを使用する最大の理由は、シングルトンを使用すると、複数のクラスを継承することで動的多相を持つことができることです。シングルトン基本クラス。


  • この多態性はどのように実装されますか。また、いくつかのユースケースは何ですか。 - underscore_d

-1

さて、シングルトンを実際に使用する理由は2つあります。一つは、誰もが言っている静的な順序のことです。

もう1つは、コードを使用しているときに、他の人がこのようなことをしないようにすることです。

CoolThing blah;
gs_coolGlobalStaticThing = blah;

さらに悪いことに、

gs_coolGlobalStaticThing = {};

カプセル化の側面は、あなたのインスタンスをバカや悪意のあるジャークから保護します。


  • あなたはアクセサについて話しています… - Flavien Volken

リンクされた質問


関連する質問

最近の質問