1. 程式人生 > >【設計模式C++】單例模式

【設計模式C++】單例模式

靜態變數的記憶體分配和初始化

全域性變數、non-local static變數(檔案域的靜態變數和類的靜態成員變數)在main執行之前就已分配記憶體並初始化;local static 變數(區域性靜態變數)同樣是在main前就已分配記憶體,第一次使用時初始化。這裡的變數包含內建資料型別和自定義型別的物件。

靜態變數初始化的執行緒安全性說明

非區域性靜態變數一般在main執行之前的靜態初始化過程中分配記憶體並初始化,可以認為是執行緒安全的;

區域性靜態變數在編譯時,編譯器的實現一般是在初始化語句之前設定一個區域性靜態變數的標識來判斷是否已經初始化,執行的時候每次進行判斷,如果需要初始化則執行初始化操作,否則不執行。這個過程本身不是執行緒安全的。C++0x之後該實現是執行緒安全的。


C++11標準針規定了區域性靜態變數初始化需要保證執行緒安全,C++03標準並無此說明,具體說明如下:
If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization


單例(Singleton)模式,也叫單子模式,是一種常用的軟體設計模式。在應用這個模式時,單例物件的類必須保證只有一個例項存在。許多時候整個系統只需要擁有一個的全域性物件,這樣有利於我們協調系統整體的行為。比如在某個伺服器程式中,該伺服器的配置資訊存放在一個檔案中,這些配置資料由一個單例物件統一讀取,然後服務程序中的其他物件再通過這個單例物件獲取這些配置資訊。這種方式簡化了在複雜環境下的配置管理。(來自wiki)

設計模式經典GoF定義的單例模式需要滿足以下兩個條件:

1)保證一個類只建立一個例項;
2)提供對該例項的全域性訪問點。

如果系統有類似的實體(有且只有一個,且需要全域性訪問),那麼就可以將其實現為一個單例。實際工作中常見的應用舉例:

1)日誌類,一個應用往往只對應一個日誌例項。
2)配置類,應用的配置集中管理,並提供全域性訪問。
3)管理器,比如windows系統的工作管理員就是一個例子,總是隻有一個管理器的例項。
4)共享資源類,載入資源需要較長時間,使用單例可以避免重複載入資源,並被多個地方共享訪問。

Lazy Singleton(懶漢模式)

首先看GoF在描述單例模式時提出的一種實現,教科書式的例子。

//標頭檔案
class Singleton
{
	public:
		static Singleton& Instance()                  //Instance()作為靜態成員函式提供裡全域性訪問點
		{
			if(ps == NULL)                        //如果還未例項化,即可例項話,反之提供例項的引用
				ps = new Singleton;
			return *ps;                           //返回指標的話可能會誤被 delete,返回引用安全一點
		}

	private:
		Singleton();                                  //這裡將構造,析構,拷貝構造,賦值函式設為私有,杜絕了生成新例
		~Singleton();
		Singleton(const Singleton&);
		Singleton& operator=(const Singleton&);

		static Singleton* ps;
};

//原始檔
Singleton* Singleton::ps = NULL;


這種方法的好處在於直到 Instance() 被訪問,才會生成例項,這種特性被稱為延遲初始化(Lazy Initialization),這在一些初始化時消耗較大的情況有很大優勢。

Lazy Singleton不是執行緒安全的,比如現在有執行緒A和執行緒B,都通過了 ps == NULL 的判斷,那麼執行緒A和B都會建立新例項。單例模式保證生成唯一例項的規則被打破了。

Eager Singleton(餓漢模式)

這種實現在編譯器初始化的時候就完成了例項的建立,和上述的Lazy Singleton相反。

//標頭檔案
class Singleton
{
	public:
		static Singleton& Instance()                  //Instance()作為靜態成員函式提供裡全域性訪問點
		{
			return instance;
		}

	private:
		Singleton();                                  //這裡將構造,析構,拷貝構造,賦值函式設為私有,杜絕了生成新例
		~Singleton();
		Singleton(const Singleton&);
		Singleton& operator=(const Singleton&);

		static Singleton instance;
};

//原始檔
Singleton Singleton::instance;

由於例項化是在初始化階段執行的,所以沒有執行緒安全的問題,但是潛在問題在於no-local static物件(函式外的static物件)在不同編譯單元(可理解為cpp檔案和其包含的標頭檔案)中的初始化順序是未定義的。如果在初始化完成之前呼叫 Instance()方法會返回一個未定義的例項。例如有兩個單例 SingletonA 和 SingletonB ,都採用了 Eager Initialization ,那麼如果 SingletonA 的初始化需要 SingletonB ,而這兩個單例又在不同的編譯單元,初始化順序是不定的,如果 SingletonA 在 SingletonB 之前初始化,就會出錯。

Meyers Singleton

為了解決上面的問題,Scott Meyers在《Effective C++》(Item 04)中的提出另一種更優雅的單例模式實現,使用local static物件(函式內的static物件)。當第一次訪問 Instance() 方法時才建立例項。

//標頭檔案
class Singleton
{
	public:
		static Singleton& Instance()                  //Instance()作為靜態成員函式提供裡全域性訪問點
		{
			static Singleton instance;
			return instance;
		}

	private:
		Singleton();                                  //這裡將構造,析構,拷貝構造,賦值函式設為私有,杜絕了生成新例
		~Singleton();
		Singleton(const Singleton&);
		Singleton& operator=(const Singleton&);		
};

C++0x之後該實現是執行緒安全的,有興趣可以讀相關的標準草案(section 6.7),編譯器的支援程度不一定,但是G++4.0及以上是支援的。

Double-Checked Locking Pattern(雙檢測鎖模式)

回顧 Lazy Singleton 模式,考慮到執行緒安全,我們可以通過加鎖來保護單例初始化這一過程,雙檢測鎖模式就是在懶漢模式的基礎上稍作修改得到:

//標頭檔案
class Singleton
{
	public:
		static Singleton& Instance()              	//Instance()作為靜態成員函式提供裡全域性訪問點
		{
			if(ps == NULL)
			{	
				Lock();				//上鎖
				if(ps == NULL)			//如果還未例項化,即可例項話,反之提供例項的引用
					ps = new Singleton;
				Unlock();			//解鎖
			}
			return *ps;                         	//返回指標的話可能會誤被 delete,返回引用安全一點
		}	

	private:
		Singleton();                                  	//這裡將構造,析構,拷貝構造,賦值函式設為私有,杜絕了生成新例
		~Singleton();
		Singleton(const Singleton&);
		Singleton& operator=(const Singleton&);

		static Singleton* ps;
};

//原始檔
Singleton* Singleton::ps = NULL;

以上的上鎖和解鎖僅用於說明,實際應用中可以使用互斥鎖,單一訊號量等方法去實現。

這裡的兩次 ps == NULL,是借鑑了Java的單例模式實現時,使用的所謂的“雙檢鎖模式”(Double-Checked Locking Pattern)。因為進行一次加鎖和解鎖是需要付出對應的代價的,而進行兩次判斷,就可以避免多次加鎖與解鎖操作,同時也保證了執行緒安全。理論上問題解決了,但是在實踐中有很多坑,如指令重排、多核處理器等問題讓DCLP實現起來比較複雜比如需要使用記憶體屏障,詳細的分析可以閱讀這篇論文

在C++11中有全新的記憶體模型和原子庫,可以很方便的用來實現DCLP。這裡不展開。有興趣可以閱讀這篇文章《Double-Checked Locking is Fixed In C++11》

pthread_once

在多執行緒程式設計環境下,儘管 pthread_once() 呼叫會出現在多個執行緒中,init_routine()函式僅執行一次,pthread_once是很適合用來實現執行緒安全單例。(pthread_once 在一個程序裡只會執行一次,其實現方式使用的就是互斥鎖+條件變數的方法)

//標頭檔案
pthread_once_t once = PTHREAD_ONCE_INIT;
class Singleton
{
	public:
		static Singleton& Instance()                  //Instance()作為靜態成員函式提供一次例項化以及全域性訪問點
		{
			pthread_once(&once, &Init);
			return *ps;
		}

		static void Init()
		{
			ps = new Singleton;
		}

	private:
		Singleton();
		~Singleton();
		Singleton(const Singleton&);
		Singleton& operator=(const Singleton&);

		static Singleton* ps;
};
//原始檔
Singleton* Singleton::ps = NULL;



總結:

單例模式的實現方法很多,要完成一個完美的實現很難,程式碼也會很複雜,但是掌握基礎的實現還是很必要的。此外還要在實際應用中不斷地去優化和探索。除了執行緒安全,一些場景下還有需要考慮資源釋放,生命週期等相關問題,可以參見《Modern C++ Design》中對Singleton的討論。

相關推薦

設計模式C++模式

靜態變數的記憶體分配和初始化 全域性變數、non-local static變數(檔案域的靜態變數和類的靜態成員變數)在main執行之前就已分配記憶體並初始化;local static 變數(區域性靜態變數)同樣是在main前就已分配記憶體,第一次使用時初始化。這裡的變數包含

C#模式<機房重構>

機房 .sh 不能 是否 gist 應用 調用方法 單例模式 sender 前言 在機房重構之前。我們學習了設計模式。在這次重構中,我們的任務就是將這些模式,加入到機房的重構中去。如今先來解決一個最簡單的問題——窗口的超生。 假設不加以限

7、C++模式/工廠模式

一、單例模式     單例模式,可以說設計模式中最常應用的一種模式了,據說也是面試官最喜歡的題目。但是如果沒有學過設計模式的人,可能不會想到要去應用單例模式,面對單例模式適用的情況,可能會優先考慮使用全域性或者靜態變數的方式,這樣比較簡單,也是沒學過設計模式的人所能想到的最簡單的方式了。

C/C++模式 Singleton

Backto C/C++ Index //.h class Singleton { public: static Singleton* instance(); static void Close(); private: Singleton(); ~Singleton

c++模式

文章目錄 1.單例模式介紹 2.餓漢模式 1)簡單瞭解 2)優點:簡單 3)缺點:啟動時可能有負載導致啟動很慢 4)程式碼實現 3.懶漢模式 1)簡單瞭解

設計模式-2模式

優點: 記憶體在系統執行過程中只有一個例項,減少記憶體開銷。 允許可變數目的例項 其他類通過唯一的全域性例項,可以方便訪問單例中的方法和變數。 單例可在第一次使用時候,進行例項化,不必在系統剛啟動就初始化。一定程度上可以控制自己例項化程序。 缺點: 過多

C++模式:餓漢模式和懶漢模式

餓漢模式:提前建立一個靜態的類物件,把所有能夠建立物件的模組全部私有化,從外部需要建立類物件時只能返回事先建立好的唯一物件。就像一個特別飢餓的人,提前準備好食物,只要餓了,就可以立刻食用。 /*惡漢模式--單例模式*/ #include<iostream> using namespa

C++模式C++實現

單例模式的概念 只允許一個產生一個物件的類單例模式的實現方法 1.單例類保證全域性只有唯一一個自行建立的例項物件 2.單例類提供獲取這個唯一例項的介面單例模式的優缺點 優點 (1)阻止其他度物件例項化

java學習筆記模式

單例設計模式:某個類在整個系統中只能有一個例項物件可被獲取和使用的程式碼模式。例如:代表JVM執行環境的Runtime類 要點: 一.一個類只能有一個例項:構造器私有化 二.該類必須自己建立這個例項:含有一個該類的靜態變數來儲存這個唯一例項 三.該類必須向整個系統提供這個例項:方式:1.直接暴露2.用

設計模式模式

amp 就會 保持 占用 back obj 線程不安全 only 大量 一、單例模式的定義 單例模式( Singleton Pattern) 是一個比較簡單的模式, 其定義如下:Ensure a class has only one instance, and provid

設計模式模式的理解與場景舉例

數據 好處 ask 配置管理 關鍵詞 簡潔 多次 nag 序列 軟件設計常用的一種設計模式 —— 單例模式 體現:在應用這個模式時,單例對象的類必須保證只有一個實例存在。 好處:許多時候整個系統只需要擁有一個全局的對象,這樣有利於我們協調整個系統的行為。 場景舉例 1.服務

設計模式模式 |建造者模式 |原型模式

單例模式 通常我們讓一個全域性變數使得一個物件被訪問,但它不能防止你例項化多個物件。一個最好的辦法就是,讓類自身負責八寸他的唯一例項。這個類可以保證沒有其他例項可以被建立,並且它可以提供一個訪問該例項的方法。 單例模式(Singletion):保證

設計模式系列-創造型模式-設計模式

單例模式定義 單例模式是一個比較簡單的模式,其定義如下:Ensure a class has only one instance,and provide a global point of access to it.確保某一個類只有一個例項,而且自行例項化並向整個系統提供這個例項。 單例模式應用場景 1

設計模式模式(Singleton)

思想 保證一個類僅有一個例項,並提供一個訪問它的全域性訪問點。 類圖 Singleton 類稱為單例類,該類的建構函式是 Private 的,這是為了禁止從 Singleton 類的外部呼叫建構函式,這就堵死了外界利用 new 建立此類的可能。通過 getInstance 方

JS設計模式模式

單例模式就如其名一樣,一個建構函式在構造出一個物件後,之後再使用這個建構函式構造物件時,不會是新的物件,依舊是上一次的物件,通俗點說,單例模式實現的是一個建構函式只能創造一個新的物件。很不解,為什麼要這麼做?這個就像什麼呢,舉個不恰當的例子,就像一家汽車製造廠,在製造完一輛車

設計模式模式、三個工廠模式

interface IFactory { Operation CreateOperation(); } class AddFactory : IFactory { public Operation CreateOperation()

設計模式 模式的幾種寫法

          單例模式是一種物件建立型模式,使用單例模式,可以保證為一個類只生成唯一的例項物件。也就是說,在整個程式空間中,該類只存在一個例項物件。     其實,GoF對單例模式的定義是:保證

設計模式——模式的幾種寫法

單例模式:保證一個類僅有一個例項,並提供一個訪問他的全域性點。 懶漢式,執行緒不安全 //懶漢式,執行緒不安全 public class Sington { private Sington(){};//讓外界不能通過new來建立例項 private

wif 系列C#之模式(Singleton)最佳實踐(一)

com value 快捷 lock 詳細介紹 筆記本 改進 奇怪 我不知道 目錄 介紹 第一個版本 ——不是線程安全的 第二個版本 —— 簡單的線程安全 第三個版本 - 使用雙重檢查鎖定嘗試線程安全 第四個版本 - 不太懶,不使用鎖且線程安全 第五版 - 完全懶惰的實例化

設計模式與Android模式——獨一無二的皇帝

什麼是單例模式 所謂單例模式,就是確保某一個類只有一個例項,而且自行例項化並向整個系統提供這個例項的設計模式。單例模式是最簡單的設計模式,也是應用最廣的設計模式。一般用於避免產生多個物件消耗過多的資