設計モードの単例モード(c++)

15504 ワード

単例モードの紹介
シングルモード(Singleton Pattern、シングルモードとも呼ばれる)は、最も広範な設計モードの1つを使用する.
単一のインスタンス・モードは、クラスが1回のみインスタンス化され、より正確には、すべてのプログラム・モジュールで共有されるインスタンス化されたオブジェクトが1つしかないクラスです.
グローバル変数に似ています.
システムのログ出力、GUIアプリケーションはシングルマウス、MODEMの接続には電話線が必要で、オペレーティングシステムにはウィンドウマネージャが1つ、PCが1つ、キーボードが1つしかないなど、多くの場所でこのような機能モジュールが必要です.
シーンを使用:
  • 全プロセスが一意のインスタンス(オブジェクト)のみを必要とする場合
  • インプリメンテーション
    以上の分析を通じて、単例モデルを実現するには、以下の点が必要である.
  • は、1つのインスタンス
  • しか生成できないことを保証する.
  • は、コンストラクション関数によってインスタンスを作成することはできません.なぜなら、可能であれば、複数のインスタンス
  • が生成される可能性があるからです.
    では、単一のクラスの構造関数はプライベートであり、次の手順を考慮することができます.
  • クラスの構造関数はプライベートであり、外部newインスタンス化
  • を防止する.
  • レプリケーションコンストラクタと=オペレータもプライベートに設定し、レプリケーションされることを防止する
  • クラスのプライベート静的ポインタ変数を使用してクラスの一意のインスタンス
  • を指す.
  • この例
  • は、共通の静的方法を使用して取得する.
    初期バージョン
    この考え方の実現は以下の通りである.
    //      
    class Singleton
    {
    private:
    		Singleton() {};
    		~Singleton() {};
    		Singleton(const Singleton&);
    		Singleton& operator=(const Singleton&);
    		
    private:
    		static Singleton* instance;
    		
    public:
    		static Singleton* getInstance()
    		{
        		if (instance == NULL)
            		instance = new Singleton();
     
       		  return instance;
    		}
    };
    
    //        
    Singleton* Singleton::instance = NULL;
    

    よく見ると、このインスタンスはオブジェクトのみが作成され、解放されず、メモリ漏洩の問題があることがわかります.
    2つの解決策があります.
  • スマートポインタ
  • を使用
  • 静的ネストクラスオブジェクト
  • を使用
    シングルスレッド実行可能バージョン
    ネストされたクラスオブジェクトの実装は次のとおりです.
    //      
    class Singleton
    {
    private:
    		Singleton() {};
    		~Singleton() {};
    		Singleton(const Singleton&);
    		Singleton& operator=(const Singleton&);
    		
    private:
    		static Singleton* instance;
    
    private:
    		class Deletor
    		{
    		public:
    				~Deletor()
    				{
    						if (Singleton::instance != NULL)
    								delete Singleton::instance;
    				}
    		};
    		static Deletor deletor;
    		
    public:
    		static Singleton* getInstance()
    		{
        		if (instance == NULL)
            		instance = new Singleton();
     
       		  return instance;
    		}
    };
    
    //        
    Singleton* Singleton::instance = NULL;
    

    プログラムの実行が終了すると、静的メンバーdeletorの構造関数が呼び出されます.この構造関数は、単一のインスタンスの一意のインスタンスを削除します.この方法には、次のような特徴があります.
  • 単一クラスの内部に固有のネストクラスを定義する
  • 単一のクラスの内部において、解放のために専用のプライベートな静的メンバー
  • を定義する.
  • プログラムにより終了時にグローバル変数の特性解放対象
  • を解析する.
    このプログラムは、単一スレッド環境で実行できますが、マルチスレッドの場合、if (instance == NULL)文に複数のスレッドが同時に入力されるなど、複数のインスタンスが作成される場合があります.
    スレッドセキュリティバージョン
    最も簡単な方法は、ロックを使用してスレッドの安全を保証することです.コードは次のとおりです.
    static Singleton* getInstance()
    {
    		if (instance == NULL)
    		{
    				Lock lock;
    				if (instance == NULL)
        				instance = new Singleton();
    		}
    	  return instance;
    }
    

    以上のようなデュアル検出ロックモード(DCL:Double-Checked Locking Pattern)は、スレッドの安全性を確保した上で、実行効率への影響を最小限に抑える.
    しかし、この実装では、1つのスレッドが7行目に実行され、もう1つのスレッドが3行目に実行され、instanceが準備されていることを発見してインスタンスに戻った場合、インスタンスがまだ構築されていない場合にエラーが発生するという問題が残っている.
    すなわち、コードの2行目:if(instance==NULL)と6行目のinstance=new Singleton()である.適切な同期がありません.
    C++11が出てこない場合は、2つのmemory barrier(メモリバリア)を挿入することでこのエラーを解決するしかありませんが、C++11はmemory modelを導入し、Atomicによってメモリの同期アクセスを実現します.すなわち、異なるスレッドは常にオブジェクトの修正前または修正後の値を取得し、オブジェクトの修正中にそのオブジェクトを取得することはできません.
    エレガントなバージョン
    また,C++11はlocal staticのマルチスレッド条件下での初期化挙動を規定し,コンパイラに内部静的変数のスレッドセキュリティを保証するように要求した.
    これにより、getInstance()メソッドに初めてアクセスした場合にのみインスタンスが作成されます.この方法はMeyers’Singletonとも呼ばれる.C++0 xの後、この実装はスレッドが安全であり、C++0 xの前にロックが必要である.
    //     
    class Singleton
    {
    private:
    		Singleton() {};
    		~Singleton() {};
    		Singleton(const Singleton&);
    		Singleton& operator=(const Singleton&);
    		
    public:
    		static Singleton& getInstance()
    		{
        		static Singleton instance;
     
       		  return instance;
    		}
    };
    

    以上のインプリメンテーションは、単一のインスタンスが最初に使用されたときにのみ初期化される、すなわち、遅延初期化は、怠け者モード(Lazy Singleton)と呼ばれる.
    餓漢版
    餓漢版(Eager Singleton):プログラムの実行時に一例のインスタンスが直ちに初期化されることを指す.
    //     
    class Singleton
    {
    private:
    		Singleton() {};
    		~Singleton() {};
    		Singleton(const Singleton&);
    		Singleton& operator=(const Singleton&);
    		
    private:
    		static Singleton instance;
    		
    public:
    		static Singleton& getInstance()
    		{
       		  return instance;
    		}
    };
    
    //        
    Singleton Singleton::instance;
    

    main関数の前に初期化されているため、スレッドが安全であるという問題はありません.
    しかし、潜在的な問題は、no-local staticオブジェクト(関数外のstaticオブジェクト)の異なるコンパイルユニットにおける初期化順序が定義されていないことである.すなわち、static Singleton instance;とstatic Singleton&getInstance()の両方の初期化順序が不確定であり、初期化が完了する前にgetInstance()メソッドを呼び出すと未定義のインスタンスが返されます.
    まとめ
  • 餓漢モードスレッドは安全であるが、潜在的な問題がある
  • 怠け者モードのローカル静的変数バージョンはC++11後にスレッドが安全であり、使用前のバージョンでは
  • をロックする必要がある.
    単例パターンが広く使われているだけに、面接でも最もよく聞かれる質問です.
    実際の学習では、実際の応用と結びつけて使用し、学んで役に立ち、単例モデルをより深く理解することができる.
    参考資料
    C++シングルモード