C++設計モード[二]単例モード

8434 ワード

次に、単一のモードである.個人的な理解は、作成方法をプライベートに変更し、内部インスタンス化して外部作成オブジェクトを禁止することです.
=============================================================================
中間コードの実現と大部分の内容は作者の所有に帰して、ただ参考にして勉強して、文章に対して転載しますかそれともオリジナルの問題に属して、許してください、自分で書いたのはすべてオリジナルに分けます
この欄は、すべて転載して転載する欄に分けられています
=============================================================================
単一例モードとは、GOFの「設計モード:オブジェクト向けソフトウェアの多重化の基礎」で、クラスにインスタンスが1つしかないことを保証し、グローバルアクセスポイントを提供することです.まず、1つのクラスに1つのインスタンスしかないことを保証する必要があります.クラスでインスタンスを構築するには、クラスのコンストラクション関数を呼び出す必要があります.このように、外部でクラスのコンストラクション関数を呼び出すことを防止するためにインスタンスを構築するには、コンストラクション関数のアクセス権をprotectedまたはprivateとマークする必要があります.最後に、グローバル・アクセス・ポイントを指定する必要がある場合は、クラス内で一意に構築されたインスタンスを返すstatic関数を定義する必要があります.意味はよく分かりますが、UMLクラス図を使って次のように表します.
UMLクラス図
コード実装
単一のパターンは,UMLクラス図から言えば,一つのクラスについて複雑な関係はない.しかし,実際のプロジェクトでは,コード実装を用いる場合,多くの点を考慮する必要がある.
実装1:
#include 
using namespace std;

class Singleton
{
public:
	static Singleton *GetInstance()
	{
		if (m_Instance == NULL )
		{
			m_Instance = new Singleton ();
		}
		return m_Instance;
	}

	static void DestoryInstance()
	{
		if (m_Instance != NULL )
		{
			delete m_Instance;
			m_Instance = NULL ;
		}
	}

	// This is just a operation example
	int GetTest()
	{
		return m_Test;
	}

private:
	Singleton(){ m_Test = 10; }
	static Singleton *m_Instance;
	int m_Test;
};

Singleton *Singleton ::m_Instance = NULL;

int main(int argc , char *argv [])
{
	Singleton *singletonObj = Singleton ::GetInstance();
	cout<GetTest()<

これは最も簡単で、最も一般的な実装方法であり、マルチスレッドの場合、改善されたバージョンである複数のSingletonインスタンスが作成される可能性があります.
実装2:
#include 
using namespace std;

class Singleton
{
public:
	static Singleton *GetInstance()
	{
		if (m_Instance == NULL )
		{
			Lock(); // C++     Lock  ,       Lock,  Boost,       
			if (m_Instance == NULL )
			{
				m_Instance = new Singleton ();
			}
			UnLock(); // C++     Lock  ,       Lock,  Boost,       
		}
		return m_Instance;
	}

	static void DestoryInstance()
	{
		if (m_Instance != NULL )
		{
			delete m_Instance;
			m_Instance = NULL ;
		}
	}

	int GetTest()
	{
		return m_Test;
	}

private:
	Singleton(){ m_Test = 0; }
	static Singleton *m_Instance;
	int m_Test;
};

Singleton *Singleton ::m_Instance = NULL;

int main(int argc , char *argv [])
{
	Singleton *singletonObj = Singleton ::GetInstance();
	cout<GetTest()<

ここで2回行いましたm_Instance==NULLの判断は,Javaの一例モード実装時に用いられるいわゆる「ダブルチェック」メカニズムを参考にしたものである.1回のロックとロック解除は対応する代価を払う必要があるため、2回の判断を行うことで、複数回のロックとロック解除操作を避けることができ、スレッドの安全を保証することができる.しかし、このような実現方法は普段のプロジェクト開発でよく使われていて、問題はありませんか?しかし、ビッグデータの操作を行うと、ロック操作は性能のボトルネックになる.そのため,新しい単一モードの実現も現れた.
実装3:
#include 
using namespace std;

class Singleton
{
public:
	static Singleton *GetInstance()
	{
		return const_cast (m_Instance);
	}

	static void DestoryInstance()
	{
		if (m_Instance != NULL )
		{
			delete m_Instance;
			m_Instance = NULL ;
		}
	}

	int GetTest()
	{
		return m_Test;
	}

private:
	Singleton(){ m_Test = 10; }
	static const Singleton *m_Instance;
	int m_Test;
};

const Singleton *Singleton ::m_Instance = new Singleton();

int main(int argc , char *argv [])
{
	Singleton *singletonObj = Singleton ::GetInstance();
	cout<GetTest()<

静的初期化は、プログラムの開始時、すなわち主関数に入る前に、主スレッドによって単一スレッドで初期化が完了するため、静的初期化インスタンスはスレッドのセキュリティを保証する.パフォーマンス要件が高い場合には、頻繁なロック解除やロック解除によるリソースの浪費を回避するために、この方法を使用することができます.上記の3つの実装は、いずれもインスタンスの破棄を考慮しなければならないため、インスタンスの破棄については分析を待つ.これにより、第4の実装形態が現れた.
実装4:
#include 
using namespace std;

class Singleton
{
public:
	static Singleton *GetInstance()
	{
		static Singleton m_Instance;
		return &m_Instance;
	}

	int GetTest()
	{
		return m_Test++;
	}

private:
	Singleton(){ m_Test = 10; };
	int m_Test;
};

int main(int argc , char *argv [])
{
	Singleton *singletonObj = Singleton ::GetInstance();
	cout<GetTest()<GetTest()<

以上が4つの主流の単例モードの実現方式である.
インスタンスの破棄
上記の4つの方法では、4つ目がnewオペレータを使用してオブジェクトをインスタンス化していない以外は、3つ目が使用されている.私たちの一般的なプログラミング観念は、new操作はdelete操作と一致する必要があるということです.はい、この観念は正しいです.上記の実装では、DestoryInstanceのstatic関数を追加しましたが、これも最も簡単で、最も一般的な処理方法です.しかし、delete操作を呼び出すのを忘れたように、DestoryInstance関数を呼び出すのを忘れがちな場合が多い.delete操作を忘れるのが怖いので、スマートポインタがあります.では、単例モデルでは、「スマート単例」はありませんが、どうすればいいのでしょうか.どうしよう?
では、まず実際のプロジェクトからお話ししましょう.実際のプロジェクト、特にクライアント開発では、このインスタンスの破棄は気にしません.なぜなら、グローバルはこのような変数で、グローバルはすべて使用しなければならないので、そのライフサイクルはソフトウェアのライフサイクルを伴って、ソフトウェアは終わって、それは自然に終わって、1つのプログラムが閉じた後に、それはその占有するメモリ資源を解放することができて、だから、いわゆるメモリの漏洩がありません.ただし、インスタンスの破棄が必要な場合は、次のような場合があります.
  • クラスには、いくつかのファイルロック、ファイルハンドル、データベース接続などがあります.これらのプログラムが閉じるにつれてすぐに閉じないリソースは、プログラムが閉じる前に手動で解放する必要があります.
  • 強迫症を有するプログラマー.

  • コード実装部の第4の方法は、第2の条件を満たすことができるが、第1の条件を満たすことはできない.コードの実装方法について説明します.
    #include 
    using namespace std;
    
    class Singleton
    {
    public:
    	static Singleton *GetInstance()
    	{
    		return m_Instance;
    	}
    
    	int GetTest()
    	{
    		return m_Test;
    	}
    
    private:
    	Singleton(){ m_Test = 10; }
    	static Singleton *m_Instance;
    	int m_Test;
    
    	// This is important
    	class GC
    	{
    	public :
    		~GC()
    		{
    			// We can destory all the resouce here, eg:db connector, file handle and so on
    			if (m_Instance != NULL )
    			{
    				cout<< "Here is the test" <GetTest()<

    プログラムの実行が終了すると、システムはSingletonの静的メンバーGCの構造関数を呼び出し、この構造関数はリソースの解放を行うが、このようなリソースの解放方式はプログラマーが「知らない」場合に行われるが、プログラマーは特に関心を持たず、単例モードのコードを使用する場合、リソースの解放に関心を持つ必要はない.では、この実現方式の原理は何でしょうか.私は問題を分析する時、問題の根まで分析するのが好きで、決してぼんやりして表面にとどまらない.プログラムが終了すると、システムはすべてのグローバル変数を自動的に解析します.実際には、これらの静的変数がグローバル変数であるように、システムもすべてのクラスの静的メンバー変数を解析します.静的変数とグローバル変数はメモリに格納されているため,解析時に同等に扱われることが分かった.
    ここでは内部GCクラスが使用されているため、このクラスの役割はリソースを解放することです.
    モード拡張
    実際のプロジェクトでは、1つのモードは私たちのコードのように簡単ではありません.さまざまな設計モードの特徴を熟練してこそ、実際のプロジェクトでよりよく運用することができます.単例モードと工場モードは実際のプロジェクトでよく見られるが,2つのモードの組み合わせはプロジェクトでもよく見られる.そこで、2つのパターンの組み合わせをまとめる必要があります.
    1つの工場で生産される製品であり、これは工場モデルの説明である.1つの工場だけで、1つの製品を生産することができます.これは単一のモデルの説明です.だから、実際には、1つの製品で、私たちは1つの工場しか必要ありません.この時、工場モデルと単例モデルの結合設計が必要です.単一のインスタンス・モードは、外部のグローバル・アクセス・ポイントを提供するため、作成する単一のデバイスを識別するために、単純なファクトリ・モードのような方法を使用して、識別を定義する必要があります.
    =============================================================================
    一つの実現方式のコード感覚はすべて渾然一体であり、実はc++コード編集時にできるはずである.h和.cppファイルを別々に編集するのが一番いいです.つまり、インタフェースと実現を別々に編集し、コードの修正と改善を容易にします.
    簡単な練習:
    //Singleton.h
    #pragma once
    #include 
    using namespace std;
    
    class Singleton
    {
    public:
    	static Singleton* GetInstance(const char* name);
    	virtual void Show() {}
    protected: //     ,       ,             
    	Singleton() {}
    private:
    	static Singleton *singleton; //       
    };
    //Singleton.cpp
    #include "Singleton.h"
    #include "SingletonA.h"
    #include "SingletonB.h"
    Singleton* Singleton::singleton = NULL;
    Singleton* Singleton::GetInstance(const char* name)
    {
    	if (singleton == NULL)
    	{
    		if (strcmp(name, "SingletonA") == 0)
    			singleton = new SingletonA();
    		else if (strcmp(name, "SingletonB") == 0)
    			singleton = new SingletonB();
    		else
    			singleton = new Singleton();
    	}
    	return singleton;
    }
    //SingletonA.h
    #pragma once
    #include "Singleton.h"
    class SingletonA : public Singleton
    {
    	friend class Singleton; //      ,               
    public:
    	void Show() { cout << "SingletonA" << endl; }
    private:   //     ,                 
    	SingletonA() {}
    };
    //SingletonB.h
    #pragma once
    #include "Singleton.h"
    class SingletonB : public Singleton
    {
    	friend class Singleton; //      ,               
    public:
    	void Show(){ cout << "SingletonB" << endl; }
    private:  //     ,                 
    	SingletonB() {}
    };
    #include "Singleton.h"
    int main()
    {
    	Singleton *st = Singleton::GetInstance("SingletonA");
    	st->Show();
    	return 0;
    }