設計模式回顧——單例模式(C++)

Acuity.發表於2020-09-27


1 前言

  上一篇文章描述了設計模式的概念和基本原則。本文描述結構簡單、目標明確且最常使用的設計模式——單例模式,以及C++的實現程式碼。


2 什麼是單例模式

  單例模式,指的是一個類只有一個例項物件,類只提供一個訪問例項物件的全域性訪介面(方法)。


3 單例模式優缺點

優點:

  • 單例模式會阻止其他物件例項化其單例物件的副本,確保唯一例項受控訪問
  • 只有一個例項物件,節約系統記憶體資源
  • 單例模式具有一定的伸縮性,類可以靈活更改例項化過程
  • 允許可變數目的例項
  • 單一例項物件,無多個例項物件共享資源的佔用問題

不足:

  • 單例模式不存在抽象層,導致單列類不便於功能擴充套件
  • 單例類“職責”(功能)綜合,一定程度上違背設計模式的“單一職責原則”
  • 單例模式導致模組之間耦合度提高
  • 濫用單例將帶來一些負面問題,如為了節省資源將資料庫連線池物件設計為的單例類,可能會導致共享連線池物件的程式過多而出現連線池溢位;如果例項化的物件長時間不被利用,系統會認為是垃圾而被回收,這將導致物件狀態的丟失

4 什麼地方使用單例模式

  單例模式一般用於物件例項被其他多個模組訪問的公共場合>,典型的應用場合有:

  • 需要頻繁例項化、銷燬物件的場合
  • 建立物件時耗時或者消耗資源過多的物件
  • 存在狀態的工具類物件
  • 頻繁訪問資料庫、檔案等耗時操作的物件

  上述應用場合,具體化的應用場景有:

  • 日誌模組輸出
  • 應用配置
  • 執行緒池操作
  • 工作管理員
  • 資源回收器

  單例模式不適用於物件可變化的場景,因為對於隨不同場景變化的物件,單例模式不能儲存彼此物件間的狀態資訊,可能引起資料錯誤。


5 單例模式實現

  單例模式實現的的關鍵要點:

  • 建構函式宣告為private,禁止類外部通過new關鍵字例項化物件

  • 拷貝建構函式同樣宣告為private,禁止拷貝建構函式例項化物件

  • 賦值運算子函式宣告成private,禁止通過賦值運算子函式例項化物件

  • 解構函式宣告為private,禁止解構函式釋放new關鍵字建立的堆上物件

  • 在類內部定義一個唯一例項物件,並宣告為static

  • 定義一個全域性訪問節點,即定義一個static方法返回唯一的例項物件


單例模式實現:

class Singleton
{
public:
	static Singleton& GetObject()
	{
		static Singleton Object;
		return Object;
	}
	void printf()
	{
		cout<<"to do"<<endl;
	}
private:
	Singleton(){}	/* 禁止建構函式例項化物件 */
	Singleton(Singleton const &);/* 禁止拷貝建構函式例項化物件 */
	Singleton& operator=(Singleton const &);/* 禁止賦值例項化物件 */
};

int main(int argc, char **argv)
{
	Singleton &a = Singleton::GetObject();
	a.printf();
	return 0;
}

  根據類的物件例項化過程的不同,單例模式又分為“懶漢式單例”和”餓漢式單例“。


5.1 餓漢式單例

  餓漢式單例指的是定義類的時候或者程式初始時執行例項化物件,使用的時候可以直接使用,無需建立。餓漢式單例,需要注意的是,採用new關鍵字生成的堆上物件,必須宣告一個public型別的方法來主動釋放物件,因為解構函式宣告為private,不會在類外被呼叫。


#include <iostream> 
#include <stdlib.h> 

using namespace std;

class Singleton
{
public:
	static Singleton* GetObject();
	void printf()
	{
		cout<<"to do"<<endl;
	}
	void DestoryObject()
	{
		delete m_Object;
		cout<<"exe destory fun"<<endl;
	}
private:
	static Singleton *m_Object;
	Singleton()/* 禁止建構函式例項化物件 */
	{
		cout<<"exe constructor fun"<<endl;
	}	
	~Singleton()/* 禁止解構函式釋放物件 */
	{
		delete m_Object;
		cout<<"exe destructor fun"<<endl;
	}
	Singleton(Singleton const &);/* 禁止拷貝建構函式例項化物件 */
	Singleton& operator=(Singleton const &);/* 禁止賦值例項化物件 */
};

Singleton* Singleton::m_Object = new Singleton();
Singleton* Singleton::GetObject()
{
	return m_Object;
}


不主動調銷燬函式:

int main(int argc, char **argv)
{
	Singleton *a = Singleton::GetObject();
	a->printf();
	return 0;
}
acuity@ubuntu:/mnt/hgfs/LSW/STHB/design-mode$ ./singleton1
exe constructor fun
to do

  很明顯,程式退出後並未執行解構函式,這就造成記憶體洩漏。


主動調銷燬函式:

int main(int argc, char **argv)
{
	Singleton *a = Singleton::GetObject();
	a->printf();
	a->DestoryObject();
	return 0;
}
acuity@ubuntu:/mnt/hgfs/LSW/STHB/design-mode$ ./singleton1
exe constructor fun
to do
exe destory fun
exe destructor fun

5.2 懶漢式單例

  懶漢式單例指的是首次需使用類物件時才例項化物件。懶漢式單例是執行緒不安全的,因此在例項化時應該加鎖處理。

  與餓漢式單例一樣,採用new關鍵字生成的堆上物件,必須宣告一個public型別的方法來主動釋放物件,因為解構函式宣告為private,不會在類外被呼叫。


懶漢式單例實現:

class Singleton
{
public:
	static Singleton& GetObject();
	void printf()
	{
		cout<<"to do"<<endl;
	}
	void DestoryObject()
	{
		delete m_Object;
		cout<<"exe destory fun"<<endl;
	}
private:
	static Singleton *m_Object;
	Singleton(){}	/* 禁止建構函式例項化物件 */
	~Singleton(){}  /* 禁止解構函式釋放物件 */
	Singleton(Singleton const &);/* 禁止拷貝建構函式例項化物件 */
	Singleton& operator=(Singleton const &);/* 禁止賦值例項化物件 */
};

Singleton& Singleton::GetObject()
{
	if (m_Object == NULL)
	{
		m_Object = new Singleton();
	}
	return m_Object;
}

執行緒安全的懶漢式單例實現:

class Singleton
{
public:
	static Singleton& GetObject();
	void printf()
	{
		cout<<"to do"<<endl;
	}
	void DestoryObject()
	{
		delete m_Object;
		cout<<"exe destory fun"<<endl;
	}
private:
	static Singleton *m_Object;
	static pthread_mutex_t m_mutex;
	Singleton()/* 禁止建構函式例項化物件 */
	{
		pthread_mutex_init(&m_mutex);
	}	
	Singleton(Singleton const &);/* 禁止拷貝建構函式例項化物件 */
	~Singleton(){}  /* 禁止解構函式釋放物件 */
	Singleton& operator=(Singleton const &);/* 禁止賦值例項化物件 */
};

Singleton& Singleton::GetObject()
{
	if (m_Object == NULL)
	{
		pthread_mutex_lock(&m_mutex);
		m_Object = new Singleton();
		pthread_mutex_lock(&m_mutex);
	}
	return m_Object;
}

5.3 餓漢式與懶漢式單例比較

  懶漢式與餓漢式單例,各有優缺點,適用於不同場景。


  • 餓漢式單例,適用於訪問量大的場景,或者多個執行緒需要訪問例項物件的場景;是一種“空間換時間”的方法

  • 懶漢式單例,適用於訪問量少的場景;是一種以“時間換空間”的方法

相關文章