309

編集:別の質問から、シングルトンに関する多くの質問/回答へのリンクを含む回答を提供しました:シングルトンに関する詳細はこちら:

だから私はスレッドSingletons: good design or a crutch?を読みました。
そして議論は今も激しさを増しています。

私はシングルトンをデザイン パターン (良い面も悪い面も) と考えています。

Singleton の問題はパターンではなく、むしろユーザーにあります (申し訳ありません)。誰もが、そしてその父親は、正しく実装できると考えています (そして、私が行った多くのインタビューから、ほとんどの人は実装できません)。また、誰もが正しいシングルトンを実装できると考えているため、パターンを悪用し、不適切な状況で使用します (グローバル変数をシングルトンに置き換えます!)。

したがって、答える必要がある主な質問は次のとおりです。

  • シングルトンを使用する必要がある場合
  • シングルトンを正しく実装するにはどうすればよいですか

この記事に対する私の希望は、Singleton をいつ (そしてどのように) 正しく使用するかについての信頼できる情報源を (複数のサイトをグーグル検索して検索するのではなく) 1 か所にまとめることができることです。また、適切な使用法と一般的な悪い実装のリストは、それらが機能しない理由と、適切な実装の場合はその弱点を説明しています。


ボールを転がしてみましょう:
私は手を挙げて、これは私が使用しているものですが、おそらく問題があると言います.
「Scott Myers」の著書「Effective C++」での主題の扱いが好きです。

シングルトンを使用するのに適した状況 (多くはありません):

  • ロギング フレームワーク
  • スレッド リサイクル プール
/*
 * C++ Singleton
 * Limitation: Single Threaded Design
 * See: http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
 *      For problems associated with locking in multi threaded applications
 *
 * Limitation:
 * If you use this Singleton (A) within a destructor of another Singleton (B)
 * This Singleton (A) must be fully constructed before the constructor of (B)
 * is called.
 */
class MySingleton
{
    private:
        // Private Constructor
        MySingleton();
        // Stop the compiler generating methods of copy the object
        MySingleton(MySingleton const& copy);            // Not Implemented
        MySingleton& operator=(MySingleton const& copy); // Not Implemented

    public:
        static MySingleton& getInstance()
        {
            // The only instance
            // Guaranteed to be lazy initialized
            // Guaranteed that it will be destroyed correctly
            static MySingleton instance;
            return instance;
        }
};

わかった。いくつかの批判と他の実装をまとめてみましょう。
:-)

4

24 に答える 24

191

答え:

次の場合はシングルトンを使用します。

  • システムには、同じタイプのオブジェクトが 1 つだけ必要です。

次の場合はシングルトンを使用しないでください。

  • メモリを節約したい
  • 新しいことに挑戦したい
  • どれだけ知っているかを見せびらかしたい
  • 他の誰もがそれをやっているからです (ウィキペディアのカーゴ カルト プログラマーを参照してください)。
  • ユーザー インターフェイス ウィジェット内
  • キャッシュのはずです
  • 文字列で
  • セッション中
  • 一日中行ける

最高のシングルトンを作成する方法:

  • 小さいほど良い。私はミニマリストです
  • スレッドセーフであることを確認してください
  • null にならないようにする
  • 一度だけ作成されていることを確認してください
  • 遅延またはシステムの初期化? あなたの要件まで
  • OS または JVM がシングルトンを作成する場合があります (たとえば、Java ではすべてのクラス定義がシングルトンです)。
  • デストラクタを提供するか、何らかの方法でリソースを破棄する方法を見つけます
  • メモリの使用量が少ない
于 2008-09-18T12:43:04.273 に答える
82

シングルトンを使用すると、1 つのクラスで 2 つの悪い特性を組み合わせることができます。それはほとんどすべての点で間違っています。

シングルトンはあなたに与えます:

  1. オブジェクトへのグローバル アクセス、および
  2. このタイプのオブジェクトを 1 つしか作成できないという保証

1つ目は簡単です。グローバルは一般的に悪いです。本当に必要でない限り、オブジェクトをグローバルにアクセス可能にするべきではありません。

2 番目は理にかなっているように聞こえるかもしれませんが、考えてみましょう。最後に、既存のオブジェクトを参照するのではなく、**誤って**新しいオブジェクトを作成したのはいつですか? これは C++ とタグ付けされているので、その言語の例を使用しましょう。うっかり書き込んでしまうことがよくありますか

std::ostream os;
os << "hello world\n";

書こうと思った時

std::cout << "hello world\n";

もちろん違います。この種のエラーは発生しないため、このエラーに対する保護は必要ありません。もしそうなら、正しい反応は、家に帰って12~20時間寝て、気分が良くなることを願うことです.

オブジェクトが 1 つだけ必要な場合は、インスタンスを 1 つ作成します。1 つのオブジェクトをグローバルにアクセス可能にする必要がある場合は、それをグローバルにします。しかし、それはそれの他のインスタンスを作成することが不可能であるべきだという意味ではありません.

「可能なインスタンスは 1 つだけ」という制約は、起こりうるバグから私たちを実際に保護するものではありません。しかし、コードのリファクタリングと保守が非常に困難になります多くの場合、後で複数のインスタンスが必要であることがわかります。複数のデータベースがあり、複数の構成オブジェクトあり、複数のロガーが必要です。一般的な例を挙げると、単体テストでは、テストごとにこれらのオブジェクトを作成および再作成できるようにする必要がある場合があります。

したがって、シングルトンが提供する両方の特性が必要な場合にのみ、シングルトン使用する必要があります。クラス(これは設計上の問題のように思えます)。これについて私が見ることができる唯一の理由は、2 つのインスタンスを作成するとアプリケーションの状態が破損するかどうかです。おそらく、クラスに多数の静的メンバーまたは同様の愚かさが含まれているためです。その場合、明白な答えはそのクラスを修正することです。唯一のインスタンスであることに依存するべきではありません。

オブジェクトへのグローバル アクセスが必要な場合は、 のようにグローバルにしstd::coutます。ただし、作成できるインスタンスの数を制限しないでください。

クラスのインスタンス数を確実に 1 つだけに制限する必要があり、2 つ目のインスタンスの作成を安全に処理する方法がない場合は、それを強制します。ただし、グローバルにアクセスできるようにしないでください。

両方の特性が必要な場合は、1) シングルトンにしてください。2) そのようなケースを想像するのに苦労しているので、それが必要な理由を教えてください。

于 2009-04-17T18:47:59.673 に答える
37

シングルトンの問題は、その実装ではありません。それは、それらが 2 つの異なる概念を混同しているということであり、どちらも明らかに望ましいものではありません。

1) シングルトンは、オブジェクトへのグローバル アクセス メカニズムを提供します。初期化順序が明確に定義されていない言語では、わずかにスレッドセーフであるか、わずかに信頼性が高い可能性がありますが、この使用法は依然としてグローバル変数と同等の道徳的方法です。これは、ぎこちない構文 (g_foo の代わりに foo::get_instance() など) で装飾されたグローバル変数ですが、まったく同じ目的 (プログラム全体でアクセス可能な単一のオブジェクト) を提供し、まったく同じ欠点があります。

2) シングルトンは、クラスの複数のインスタンス化を防ぎます。IME さん、この種の機能をクラスに組み込むことはめったにありません。通常、これはより文脈に即したものです。唯一無二と考えられているものの多くは、実際にはたまたま唯一のものです。IMO より適切な解決策は、複数のインスタンスが必要であることに気付くまで、インスタンスを 1 つだけ作成することです。

于 2008-09-17T19:27:56.633 に答える
29

パターンに関する 1 つのこと:一般化しないでください。役に立つ場合もあれば、失敗する場合もすべてあります。

コードをテストする必要がある場合、シングルトンは厄介な場合があります。通常は、クラスの 1 つのインスタンスで行き詰っており、コンストラクターでドアを開くか、状態をリセットするメソッドなどを選択できます。

もう1つの問題は、シングルトンが実際には変装したグローバル変数にすぎないことです。プログラム全体でグローバルな共有状態が多すぎると、物事が元に戻る傾向があることは誰もが知っています。

依存関係の追跡が難しくなる可能性があります。すべてが Singleton に依存している場合、Singleton を変更したり、2 つに分割したりするのは難しくなります。これも柔軟性を妨げます。この問題を軽減するために、いくつかの依存性注入フレームワークを調査してください。

于 2008-09-17T19:26:48.477 に答える
14

基本的に、シングルトンを使用すると、複雑なグローバル変数を持つことが困難または不可能になる言語で複雑なグローバル状態を持つことができます。

特にJavaは、すべてがクラス内に含まれている必要があるため、グローバル変数の代わりにシングルトンを使用します。グローバル変数に最も近いのは public static 変数で、グローバル変数のように使用できます。import static

C++ にはグローバル変数がありますが、グローバル クラス変数のコンストラクターが呼び出される順序は定義されていません。そのため、シングルトンを使用すると、変数が初めて必要になるまでグローバル変数の作成を延期できます。

Python や Ruby などの言語では、代わりにモジュール内でグローバル変数を使用できるため、シングルトンをほとんど使用しません。

では、シングルトンを使用するのが良い/悪いのはいつですか? グローバル変数を使用するのが良い/悪い場合とほぼ正確です。

于 2008-09-17T19:24:58.120 に答える
6
  • シングルトンを正しく実装するにはどうすればよいですか

私が今まで見たことがない問題が1つあります。それは、前の仕事で遭遇した問題です。DLL間で共有されるC++シングルトンがあり、クラスの単一インスタンスが機能しないことを保証する通常のメカニズムがありました。問題は、各DLLがEXEとともに独自の静的変数のセットを取得することです。get_instance関数がインラインまたは静的ライブラリの一部である場合、各DLLは「シングルトン」の独自のコピーになります。

解決策は、シングルトンコードが1つのDLLまたはEXEでのみ定義されていることを確認するか、インスタンスを分割するためにそれらのプロパティを使用してシングルトンマネージャーを作成することです。

于 2009-04-17T18:50:59.493 に答える
6

Alexandrescu によるModern C++ Designには、スレッドセーフで継承可能なジェネリック シングルトンがあります。

私の 2p 価値については、シングルトンの有効期間を定義することが重要だと思います (それらを使用することが絶対に必要な場合)。私は通常、静的get()関数に何もインスタンス化させず、セットアップと破棄をメイン アプリケーションの専用セクションに任せます。これは、シングルトン間の依存関係を強調するのに役立ちますが、上で強調したように、可能であればそれらを避けるのが最善です。

于 2008-09-18T12:29:04.667 に答える
5

最初の例はスレッドセーフではありません-2つのスレッドが同時にgetInstanceを呼び出す場合、その静的はPITAになります。何らかの形のミューテックスが役立ちます。

于 2008-09-17T19:20:13.687 に答える
5

他の人が指摘したように、シングルトンの主な欠点には、シングルトンを拡張できないこと、およびテスト目的などで複数のインスタンスをインスタンス化する力を失うことが含まれます。

シングルトンの便利な側面:

  1. 遅延インスタンス化または先行インスタンス化
  2. セットアップや状態が必要なオブジェクトに便利

ただし、これらの利点を得るためにシングルトンを使用する必要はありません。作業を行う通常のオブジェクトを作成し、ファクトリ (別のオブジェクト) を介して人々にアクセスさせることができます。ファクトリは、必要に応じて、1 つだけをインスタンス化し、それを再利用するなどについて心配することができます。また、具象クラスではなくインターフェイスにプログラムする場合、ファクトリは戦略を使用できます。つまり、インターフェイスのさまざまな実装を切り替えることができます。

最後に、ファクトリは、Spring などの依存性注入テクノロジに適しています。

于 2009-09-25T16:30:14.083 に答える
4

シングルトンは、初期化してオブジェクトを作成するときに実行されるコードが多い場合に便利です。たとえば、永続オブジェクトをセットアップするときに iBatis を使用する場合、コードに到達する前に、すべての構成を読み取り、マップを解析し、すべてが正しいことを確認する必要があります。

これを毎回行うと、パフォーマンスが大幅に低下します。シングルトンで使用すると、そのヒットを 1 回受ければ、その後のすべての呼び出しでそれを行う必要がなくなります。

于 2008-09-17T19:28:22.590 に答える
3

シングルトンの本当の欠点は、継承を壊すことです。シングルトンが参照されているコードにアクセスできない限り、拡張機能を提供する新しいクラスを派生させることはできません。そのため、Singleton がコードを密結合にする (戦略パターンによって修正可能 ... 別名依存性注入) という事実を超えて、リビジョン (共有ライブラリ) からコードのセクションを閉じることもできなくなります。

したがって、ロガーやスレッド プールの例でさえ無効であり、戦略に置き換える必要があります。

于 2008-09-17T19:33:19.770 に答える
3

ほとんどの人は、グローバル変数を使用して気分を良くしようとするときにシングルトンを使用します。正当な用途はありますが、ほとんどの場合、それらを使用する場合、インスタンスが 1 つしか存在できないという事実は、グローバルにアクセスできるという事実に比べれば些細な事実です。

于 2009-04-17T19:09:19.043 に答える
3

シングルトンでは 1 つのインスタンスしか作成できないため、インスタンスのレプリケーションを効果的に制御します。たとえば、ルックアップの複数のインスタンスは必要ありません-たとえばモールスルックアップマップなど、シングルトンクラスでラップするのが適切です。また、クラスのインスタンスが 1 つしかないからといって、そのインスタンスへの参照の数も制限されているわけではありません。インスタンスへの呼び出しを (スレッド化の問題を回避するために) キューに入れ、必要な変更を行うことができます。はい、シングルトンの一般的な形式はグローバルに公開されたものです。設計を変更して、よりアクセス制限されたシングルトンを作成できます。これまで疲れたことはありませんが、それが可能であることは確かです。そして、シングルトン パターンはまったく悪であるとコメントしたすべての人は、次のことを知っておく必要があります。

于 2009-08-01T12:41:53.303 に答える
2

しかし、Singleton のようなものが必要な場合、Schwarz Counterを使用してインスタンス化することがよくあります。

于 2008-09-17T19:44:56.403 に答える
1

私は面接テストとしてシングルトンを使用します。

開発者にいくつかのデザインパターンに名前を付けるように頼んだとき、彼らが名前を付けることができるのがシングルトンだけである場合、それらは採用されません。

于 2008-09-17T19:40:26.430 に答える
0

シングルトンがグローバルでなければならない理由はまだわかりません。

クラス内にプライベート定数静的変数としてデータベースを隠し、データベースをユーザーに公開することなくデータベースを利用するクラス関数を作成するシングルトンを作成するつもりでした。

なぜこの機能が悪いのかわかりません。

于 2014-01-31T03:35:23.977 に答える
0

Anti-Usage:

One major problem with excessive singleton usage is that the pattern prevents easy extension and swapping of alternate implementations. The class-name is hard coded wherever the singleton is used.

于 2008-09-17T19:26:16.777 に答える
0

これはC#の最も堅牢なバージョンだと思います。

using System;
using System.Collections;
using System.Threading;

namespace DoFactory.GangOfFour.Singleton.RealWorld
{

  // MainApp test application

  class MainApp
  {
    static void Main()
    {
      LoadBalancer b1 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b2 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b3 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b4 = LoadBalancer.GetLoadBalancer();

      // Same instance?
      if (b1 == b2 && b2 == b3 && b3 == b4)
      {
        Console.WriteLine("Same instance\n");
      }

      // All are the same instance -- use b1 arbitrarily
      // Load balance 15 server requests
      for (int i = 0; i < 15; i++)
      {
        Console.WriteLine(b1.Server);
      }

      // Wait for user
      Console.Read();    
    }
  }

  // "Singleton"

  class LoadBalancer
  {
    private static LoadBalancer instance;
    private ArrayList servers = new ArrayList();

    private Random random = new Random();

    // Lock synchronization object
    private static object syncLock = new object();

    // Constructor (protected)
    protected LoadBalancer()
    {
      // List of available servers
      servers.Add("ServerI");
      servers.Add("ServerII");
      servers.Add("ServerIII");
      servers.Add("ServerIV");
      servers.Add("ServerV");
    }

    public static LoadBalancer GetLoadBalancer()
    {
      // Support multithreaded applications through
      // 'Double checked locking' pattern which (once
      // the instance exists) avoids locking each
      // time the method is invoked
      if (instance == null)
      {
        lock (syncLock)
        {
          if (instance == null)
          {
            instance = new LoadBalancer();
          }
        }
      }

      return instance;
    }

    // Simple, but effective random load balancer

    public string Server
    {
      get
      {
        int r = random.Next(servers.Count);
        return servers[r].ToString();
      }
    }
  }
}

.NET 最適化バージョンは次のとおりです。

using System;
using System.Collections;

namespace DoFactory.GangOfFour.Singleton.NETOptimized
{

  // MainApp test application

  class MainApp
  {

    static void Main()
    {
      LoadBalancer b1 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b2 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b3 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b4 = LoadBalancer.GetLoadBalancer();

      // Confirm these are the same instance
      if (b1 == b2 && b2 == b3 && b3 == b4)
      {
        Console.WriteLine("Same instance\n");
      }

      // All are the same instance -- use b1 arbitrarily
      // Load balance 15 requests for a server
      for (int i = 0; i < 15; i++)
      {
        Console.WriteLine(b1.Server);
      }

      // Wait for user
      Console.Read();    
    }
  }

  // Singleton

  sealed class LoadBalancer
  {
    // Static members are lazily initialized.
    // .NET guarantees thread safety for static initialization
    private static readonly LoadBalancer instance =
      new LoadBalancer();

    private ArrayList servers = new ArrayList();
    private Random random = new Random();

    // Note: constructor is private.
    private LoadBalancer()
    {
      // List of available servers
      servers.Add("ServerI");
      servers.Add("ServerII");
      servers.Add("ServerIII");
      servers.Add("ServerIV");
      servers.Add("ServerV");
    }

    public static LoadBalancer GetLoadBalancer()
    {
      return instance;
    }

    // Simple, but effective load balancer
    public string Server
    {
      get
      {
        int r = random.Next(servers.Count);
        return servers[r].ToString();
      }
    }
  }
}

このパターンはdotfactory.comで見つけることができます。

于 2008-09-17T19:27:22.390 に答える
0

Meyers のシングルトン パターンは、ほとんどの場合十分に機能します。コンストラクターがスローされず、シングルトン間に依存関係がない限り。

すべての GAO がシングルトンであるとは限りませんが、シングルトンはグローバルにアクセス可能なオブジェクト(以降、GAO) の実装です。

ロガー自体はシングルトンであってはなりませんが、ログメッセージが生成される場所とログが記録される場所または方法を分離するために、ログを記録する手段は理想的にはグローバルにアクセスできる必要があります。

遅延読み込み/遅延評価は別の概念であり、シングルトンは通常それも実装します。それには多くの独自の問題が伴います。特に、スレッド セーフや、当時は良いアイデアのように思えたことが結局はあまり良くないというような例外で失敗した場合の問題です。(文字列での COW 実装に少し似ています)。

それを念頭に置いて、GOA は次のように初期化できます。

namespace {

T1 * pt1 = NULL;
T2 * pt2 = NULL;
T3 * pt3 = NULL;
T4 * pt4 = NULL;

}

int main( int argc, char* argv[])
{
   T1 t1(args1);
   T2 t2(args2);
   T3 t3(args3);
   T4 t4(args4);

   pt1 = &t1;
   pt2 = &t2;
   pt3 = &t3;
   pt4 = &t4;

   dostuff();

}

T1& getT1()
{
   return *pt1;
}

T2& getT2()
{
   return *pt2;
}

T3& getT3()
{
  return *pt3;
}

T4& getT4()
{
  return *pt4;
}

それはそれほど大雑把に行う必要はありません。明らかに、オブジェクトを含むロードされたライブラリでは、オブジェクトの寿命を管理するために他のメカニズムが必要になるでしょう。(ライブラリをロードしたときに取得するオブジェクトに入れます)。

シングルトンを使用する場合は?私はそれらを 2 つの目的で使用しました - どのライブラリが dlopen でロードされたかを示すシングルトン テーブル - ロガーがサブスクライブでき、メッセージを送信できるメッセージ ハンドラ。特にシグナル ハンドラに必要です。

于 2011-03-02T13:09:27.017 に答える
-1

デスクトップアプリ(私は知っています、恐竜だけがもうこれらを書いています!)は、比較的不変のグローバルアプリケーション設定(ユーザー言語、ヘルプファイルへのパス、ユーザー設定など)を取得するために不可欠です。そうしないと、すべてのクラスとすべてのダイアログに伝播する必要があります。 。

編集-もちろん、これらは読み取り専用である必要があります!

于 2008-09-17T19:39:40.410 に答える
-1

別の実装

class Singleton
{
public:
    static Singleton& Instance()
    {
        // lazy initialize
        if (instance_ == NULL) instance_ = new Singleton();

        return *instance_;
    }

private:
    Singleton() {};

    static Singleton *instance_;
};
于 2012-01-07T17:10:33.110 に答える