前言
最近個人在寫一些小的練手的專案,發現單例模式的應用實在是很廣泛,很多情況下乙個類都需要使用單例模式來實現,因此這裡我再度總結一下單例模式的實現,之前已經總結過一次,當時的例子給的是muduo的單例模式實現,這次再給出幾種其他的實現,同時進行一下比較。
餓漢模式
餓漢模式就是在還未使用變數的時候,已經對該變數進行賦值,有點類似很飢餓的感覺,在main函式開始執行前就已經初始化好了,因此是執行緒安全的。
不考慮析構函式的餓漢模式實現
#include
using
namespace std;
class
singleton
;private
:singleton
(const singleton&);
singleton&
operator=(
const singleton&);
static singleton* m_instance;
public
:static singleton*
getinstance()
;~singleton()
};singleton* singleton::
getinstance()
singleton* singleton::m_instance =
new singleton;
intmain()
餓漢模式的優點
執行緒安全
簡單易懂易實現
餓漢模式的缺點
不適合對效能有要求的場景,因為有些場景下可能不會用到這個變數,這樣就無需載入
在main函式之前就已經初始化完成,基本上沒辦法給類傳入引數
懶漢模式
這個從名字上更容易理解,懶漢就是不到萬不得已絕不行動,對於單例模式來說就是預設情況下變數不初始化為nullptr,而在getinstance方法被呼叫的時候才會判斷instance是否已經被初始化,然後再決定是否要初始化,不過這個行為導致的乙個重要問題就是執行緒不安全。
不考慮析構函式的懶漢模式實現
#include
using
namespace std;
class
singleton
;private
:singleton
(const singleton&);
singleton&
operator=(
const singleton&);
static singleton* m_instance;
public
:static singleton*
getinstance()
;~singleton()
};singleton* singleton::
getinstance()
return m_instance;
}singleton* singleton::m_instance =
null
;int
main()
解決懶漢模式執行緒不安全的幾種方法
使用區域性靜態變數
區域性靜態變數的初始化是執行緒安全的,這一點由編譯器保證.( 這是乙個 gcc 的 patch,專門解決這個問題)。會在程式退出的時候自動銷毀。不過需要注意的是,這個方法適合 c++11,c++11保證靜態區域性變數的初始化是執行緒安全的。如果是 c++98 就不能用這個方法。
class
sprivate:s
()s(s const&)
=delete
;// don't implement.
void
operator
=(s const&)
=delete
;// don't implement
};
加鎖
這個辦法應該是最簡單最直接的了,**實現如下:
// singleton.h
class
singleton
return p;
}private
: std::mutex mutex_;
static singleon *p;
singleton()
singleton
(const singleton &)=
delete
; singleton&
operator=(
const singleton &)=
delete;}
;// singleton.cpp
singleton *singleton::p =
null
;
這個方法的缺點是每次呼叫getinstance都會加鎖,在多執行緒的情況下可能會輕微增加程式的負擔。
pthread_once
這個是我在以前推薦的部落格裡面寫的,陳碩推薦的單例模式實現,是乙個非常好的實現方式,不過僅限於在linux系統下
// singleton.h
class
singleton
private
:singleton()
singleton
(const singleton &)=
delete
; singleton&
operator=(
const singleton &)=
delete
;static
void
init()
static pthread_once_t ponce_;
static singleton *value_;};
// singleton.cpp
pthread_once_t singleton::ponce_ = pthread_once_init;
singleton* singleton::value_ =
null
;
double check locking(dcl)
double check locking. 只能用記憶體屏障,其他做法都是有問題的。普通的 double check 之所以錯,是因為亂序執行和多處理器下,不同 cpu 中間 cache 刷往記憶體並對其他 cpu 可見的順序無法保障(cache coherency problem)。比如
singleton*p = new singleton;這個語句的實際執行過程有三步:
分配記憶體
構造物件
賦值給p
由於亂序執行,因此2和3的執行順序實際上是不一定的。因此,如果在某些情況下,3先於2執行,那麼就會出現p實際上指向乙個無效的物件,但是由於p已經不再是空指標,所以在dcl的第一步判斷p是否為空指標時,順利通過,然後p被返回,然而此時的p指向的記憶體是位被構造的。
可能有同學認為可以增加乙個臨時變數temp_p,當temp_p構造完成之後再賦值給p,但是對於現代的編譯器這樣做是沒有意義的,因為現代編譯器會認為temp_p是無效的,可能會被優化掉,因此就回到了上乙個問題,因此dcl不是執行緒安全的。
自動呼叫析構函式
在前面說的懶漢模式不考慮析構函式和餓漢模式不考慮析構函式中,**中我們雖然都寫了析構函式,但是如果我們執行的話會發現,其實析構函式並不會被呼叫,這是為什麼呢,其實答案很簡單,因此我們的物件是new出來的,然而我們並沒有顯式的呼叫delete,因此析構函式自然不會被呼叫,那麼我們是需要手動呼叫析構函式嗎,其實並非如此,我們希望一種可以自動呼叫析構函式的方式來實現,其實也很簡單,就是之前說的區域性靜態變數,實現如下,其實和之前一樣:
#include
using
namespace std;
class
singleton
;private
:singleton
(const singleton&);
singleton&
operator=(
const singleton&);
public
:static singleton*
getinstance()
;~singleton()
};singleton* singleton::
getinstance()
intmain()
其實寫到這裡基本就差不多了,不過這個方法的缺點就是只有在c++11標準下才能保證其正確性,不過從目前來看,c++11的普及性已經很好了,所以基本上不怎麼需要擔心這個問題
總結其實說到這裡,單例模式就講的差不多了,陳碩推薦的pthread_once方法和c++11下靜態區域性變數的方法都是很不錯的,值得學習。
C 中實現單例模式
單例模式是軟體工程中廣為人知的設計模式。單例模式就是指乙個永遠只能例項化一次。使用的方式是呼叫類裡建立的靜態方法。通常來說,單例模式建立的類,都是不帶形參的 原因就是當建立多個例項的時候,如果引數不同的話 比如2個不同的過載建構函式 那麼就會造成一些不必要的問題 如果相同的例項要被建立而且他們使用相...
單例模式總結 C
單例模式 保證在整個程式中只有乙個例項,並提供乙個各個程式模組都可以訪問的介面。一 常用標準模式 include using namespace std class singleton int m x static singleton m instance public static singlet...
C 單例模式總結
c 單例模式總結 單例模式可以說是在開發過程中最常用的一種設計模式了,一般很多業務處理層都會實現單例模式。單例模式分為懶漢式和餓漢式,懶漢式是在呼叫的時候生成唯一的例項,餓漢式是在系統初始化的時候就實現例項。這裡有乙個有意思的問題,如何在不知道程式的時候,判斷單例是懶漢式或者是餓漢式?單例的實現是有...