天天看點

軟體開發常用設計模式—單例模式總結(c++版)

單例模式:就是隻有一個執行個體。

singleton pattern單例模式:確定某一個類在程式運作中隻能生成一個執行個體,并提供一個通路它的全局通路點。這個類稱為單例類。如一個工程中,資料庫通路對象隻有一個,電腦的滑鼠隻能連接配接一個,作業系統隻能有一個視窗管理器等,這時可以考慮使用單例模式。

衆所周知,c++中,類對象被建立時,編譯系統為對象配置設定記憶體空間,并自動調用構造函數,由構造函數完成成員的初始化工作,也就是說使用構造函數來初始化對象。

1、那麼我們需要把構造函數設定為私有的 private,這樣可以禁止别人使用構造函數建立其他的執行個體。

2、又單例類要一直向系統提供這個執行個體,那麼,需要聲明它為靜态的執行個體成員,在需要的時候,才建立該執行個體。

3、且應該把這個靜态成員設定為 null,在一個public 的方法裡去判斷,隻有在靜态執行個體成員為 null,也就是沒有被初始化的時候,才去初始化它,且隻被初始化一次。

通常我們可以讓一個全局變量使得一個對象被通路,但它不能阻止你執行個體化多個對象。如果采用全局或者靜态變量的方式,會影響封裝性,難以保證别的代碼不會對全局變量造成影響。

一個最好的辦法是,讓類自身負責儲存它的唯一執行個體。這個類可以保證沒有其他執行個體可以被建立,并且它可以提供一個通路該執行個體的方法,單例模式比全局對象好還包括,單例類可以繼承。

單例模式又分為兩種基本的情形:餓漢式和懶漢式

直接在靜态區初始化 instance,然後通過 get 方法傳回,這樣這個類每次直接先生成一個對象,好像好久沒吃飯的餓漢子,急着吃飯一樣,急切的 new 對象,這叫做餓漢式單例類。或者是在 get 方法中才 new instance,然後傳回這個對象,和懶漢字一樣,不主動做事,需要調用 get 方法的時候,才 new 對象,這就叫做懶漢式單例類。

如下是懶漢式單例類

軟體開發常用設計模式—單例模式總結(c++版)
軟體開發常用設計模式—單例模式總結(c++版)

執行個體化了1個對象!

program ended with exit code: 0

小結:

懶漢式單例模式是用時間換取控件,餓漢式單例模式,是用空間換取時間。

繼續分析,考慮多線程下的懶漢式單例模式

上述代碼在單線程的情況下,運作正常,但是遇到了多線程就出問題,假設有兩個線程同時運作了這個單例類,同時運作到了判斷 if 語句,并且當時,instance 執行個體确實沒有被初始化呢,那麼兩個線程都會去運作并建立執行個體,此時就不滿足單例類的要求了。那麼我們需要寫上線程同步的功能。

軟體開發常用設計模式—單例模式總結(c++版)
軟體開發常用設計模式—單例模式總結(c++版)

此時,還是有 ab 兩個線程來運作這個單例類,由于在同一時刻,隻有一個線程能拿到同步鎖(互斥信号量機制),a 拿到了同步鎖,b 隻能等待,如果 a發現執行個體還沒建立,a 就會建立一個執行個體,建立完畢,a 釋放同步鎖,然後 b 才能拿到同步鎖,繼續運作接下來的代碼,b 發現 a 線程運作的時候,已經生成了一個執行個體,b 線程就不會重複建立執行個體了,這樣就保證了我們在多線程環境中隻能得到一個執行個體。

繼續分析多線程下的懶漢式單例模式

代碼中,每次 get 方法中,得到 instance,都要判斷是否為空,且判斷是否為空之前,都要先加同步鎖,如果線程很多的時候,就要先等待加了同步鎖的線程運作完畢,才能繼續判斷餘下的線程,這樣就會造成大量線程的阻塞,且加鎖是個非常消耗時間的過程,應該盡量避免(除非很有必要的時候)。可行的辦法是,雙重判斷方法。

因為,隻是在執行個體還沒有建立的時候,需要加鎖判斷,保證每次隻有一個線程建立執行個體,而當執行個體已經建立之後,其實就不需要加鎖操作了。

雙重判斷的線程安全的懶漢式單例模式 

軟體開發常用設計模式—單例模式總結(c++版)
軟體開發常用設計模式—單例模式總結(c++版)

這樣的雙重檢測機制,提高了單例模式在多線程下的效率,因為這樣的代碼,隻需要在第一次建立執行個體的時候,需要加鎖,其他的時候,線程無需排隊等待加鎖之後,再去判斷了,比較高效。

再看餓漢式的單例模式,之前看了懶漢式的單例類,是線程不安全的,通過加鎖(雙重鎖),實作線程安全

回憶餓漢式單例類:直接在靜态區初始化 instance,然後通過 get 方法傳回,這樣這個類每次直接先生成一個對象,好像好久沒吃飯的餓漢子,急着吃飯一樣,急切的 new 對象,這叫做餓漢式單例類。

軟體開發常用設計模式—單例模式總結(c++版)
軟體開發常用設計模式—單例模式總結(c++版)

注意:靜态初始化執行個體可以保證線程安全,因為靜态執行個體初始化在程式開始時進入主函數之前,就由主線程以單線程方式完成了初始化!餓漢式的單例類,也就是靜态初始化執行個體保證其線程安全性,故在性能需求較高時,應使用這種模式,避免頻繁的鎖争奪。

繼續看單例模式

上面的單例模式沒有 destory() 方法,也就是說,貌似上面的單例類沒有主動析構這個唯一執行個體!然而這就導緻了一個問題,在程式結束之後,該單例對象沒有delete,導緻記憶體洩露!下面是一些大神的方法:一個妥善的方法是讓這個類自己知道在合适的時候把自己删除,或者說把删除自己的操作挂在作業系統中的某個合适的點上,使其在恰當的時候被自動執行。

我們知道,程式在結束的時候,系統會自動析構所有的全局變量。事實上,系統也會析構所有的類的靜态成員變量,就像這些靜态成員也是全局變量一樣。如果在類的析構行為中有必須的操作,比如關閉檔案,釋放外部資源,那麼上面的代碼無法實作這個要求。我們需要一種方法,正常的删除該執行個體。利用這些特征,我們可以在單例類中定義一個這樣的靜态成員變量,而它的唯一工作就是在析構函數中删除單例類的執行個體。如下面的代碼中的garbage類:

軟體開發常用設計模式—單例模式總結(c++版)
軟體開發常用設計模式—單例模式總結(c++版)

調用了單例類的構造函數

a = b

單例類的唯一執行個體被析構了

調用了單例類的析構函數

類garbage被定義為singleton的内嵌類,以防該類在其他地方濫用,程式運作結束時,系統會調用singleton的靜态成員garbage的析構函數,該析構函數會删除單例的唯一執行個體,使用這種方法釋放單例對象有以下特征:

1、在單例類内部定義專有的嵌套類;

2、在單例類内定義私有的專門用于釋放的靜态成員;

3、利用程式在結束時析構全局變量的特性,選擇最終的釋放時機;

4、使用單例的代碼不需要任何操作,不必關心對象的釋放。

其實,繼續想單例類的實作,有的人會這樣做:

在程式結束時調一個專門的方法,這個方法裡判斷執行個體對象是否為 null,如果不為 null,就對傳回的指針掉用delete操作。這樣做可以實作删除單例的功能,但不僅很醜陋,而且容易出錯。因為這樣的附加代碼很容易被忘記,而且也很難保證在delete之後,沒有代碼再調用getinstance函數。不推薦直接的删除方法。

繼續檢視單例模式:單例模式在實際開發過程中是很有用的,單例模式的特征總結:

1、一個類隻有一個執行個體

2、提供一個全局通路點

3、禁止拷貝

逐個分析:

1、實作隻有一個執行個體,需要做的事情:将構造函數聲明為私有

2、提供一個全局通路點,需要做的事情:類中建立靜态成員和靜态成員方法

3、禁止拷貝:把拷貝構造函數聲明為私有,并且不提供實作,将指派運算符聲明為私有,防止對象的指派

完整的單例類實作代碼如下:

軟體開發常用設計模式—單例模式總結(c++版)
軟體開發常用設計模式—單例模式總結(c++版)

單例類de測試,兩個方法:

1、執行個體化多個對象,看調用了幾次構造函數,如果隻調用一次,說明隻建立一個執行個體

2、單步跟蹤,檢視對象的位址,是否一樣,一樣則為一個對象

辛苦的勞動,轉載請注明出處,謝謝……

http://www.cnblogs.com/kubixuesheng/p/4355055.html

繼續閱讀