C++深入理解單例模式詳解
作者:知乎用戶
鏈接:https://www.zhihu.com/question/27704562/answer/37760739
來源:知乎
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
不使用編譯器擴展,不用C++11,不加鎖,也不使用原子操作的話,
那必須有個條件就是main函數執行之前程序必須是單線程的,不然真不行,再百度也不行,
如果有符合以上條件的單例類且不要求main之前是單線程的,請告訴我....然後:
這是2b單例:template<typename T>
class Im2Bsingleton
{
public:
static T* get() {
if(ptr==nullptr) { ptr=new T; }
return ptr;
};
private:
static T* ptr;
};
這個顯然不是線程安全的,因爲ptr會在第一次被使用的時候new T,
而第一次被使用的時候8成是main之後了,就很可能是多線程情況下,
那麼可以使用一個原子操作:template<typename T>
class AtomicSingleton
{
public:
static T* get() {
for(;;) {
T* k=nullptr;
ptr.compare_exchange_weak(k,(T*)&place_holder);
if(k==0){
k=new T;
ptr.set(k);//此處必然成功
return k;
} else if(k==(T*)&place_holder) {
//別的線程佔坑了,等着去吧!
} else {
return k;
}
}
};
private:
static atomic<T*> ptr;
static char place_holder;
};
代價是一次CAS和一次判斷,但是題目說了不能加鎖或者原子操作,那麼,
再說一遍就是必須保證main函數執行之前不能存在多線程情況,
可以這樣:template<typename T>
class Singleton
{
public:
static T* get() {
return ptr;
};
private:
static T* ptr;
};
你肯定覺得這個不對啊,ptr沒被構造啊,對,保證前提的基礎上,
我們只需要保證ptr能在main之前絕逼被構造就行了,
我們需要一個helper:普通單例
:template<typename T>
class Singleton
{
private:
struct ShitCleanHelper {
ShitCleanHelper(){ Singleton<T>::ptr=new T; }
~ShitCleanHelper(){
delete Singlenton<T>::ptr;
Singlenton<T>::ptr=nullptr;
}
};
public:
static T* get() {
return ptr;
};
private:
static T* ptr;
static ShitCleanHelper helper;
friend class ShitCleanHelper;
};
由於helper是static的,根據C++標準,靜態對象被保證在main之前構造,
因此ShitCleanHelper被絕對保證在main之前構造,它在構造時候不幹任何事,
只負責new T.這樣,當進入main之後,ptr肯定就指着正確的對象了.
你會說那又不對了,這樣雖然能夠保證Singlenton在main之前new,
但是萬一我需要在main之前就要用get()怎麼辦?
又或者,我的模塊與模塊之間不同的static對象互相有交互,這一切都發生在main之前,怎麼辦?
下面是文藝單例:template<typename T,bool DirectConstruction = true>
class ScopedSingleton
{
private:
class InstanceCreator {
public:
InstanceCreator() :m_ptr(new T) {};
~InstanceCreator() { delete m_ptr; };
operator T&() noexcept { return *m_ptr;}
private:
T* RESTRICT const m_ptr;
};
class DummyInstanceUser
{
public:
DummyInstanceUser(){ ScopedSingleton::getInstance(); }
~DummyInstanceUser() { ScopedSingleton::getInstance(); }
void DoNothing(){}
};
public:
static T& getInstance()
{
static InstanceCreator m_instancePtr;
m_dummyUser.DoNothing();
//此處似乎是模板的一個bug,加了這一句才能保證main之前m_instancePtr一定被構造.
return m_instancePtr;
};
private:
ScopedSingleton(){ getInstance(); }
~ScopedSingleton(){ getInstance(); }
private:
static DummyInstanceUser m_dummyUser;
};
首先,C++保證函數內的static變量保證在它第一次被使用之前被構造,
這就避免了"普通單例"中可能在ShitCleanHelper構造new T之前就要使用的尷尬局面.
無論什麼時候要用它,它肯定會在這之前被構造了,但是這樣一來又有問題了,
假如main函數之前誰都不用它,而main之後纔有人第一次用到它,
那還怎麼保證它在main之前被構造以避免多線程問題呢?
這就需要DummyInstanceUser出現了, DummyInstanceUser存在的唯一一個實例是m_dummyUser,
由於它是static的,所以它保證了在main函數之前被構造,而它的構造函數的唯一目的就是"假裝使用了m_instancePtr",
這樣即使沒有人再main之前調用getInstance(),DummyUser依然會作爲main之前的最後一道屏障保證在main之前一定構造成功.同時,這種單例還避免了跨模塊的static變量互相引用的問題,例如有不同的單例A和B,它們在兩個不同的cpp中,則它們的構造順序雖然都在main之前,但互相之間的順序是不確定的,但是使用這種文藝單例,如果A需要用到B,請看這一段文字的第一句話,只要A會用到B,B就會保證在A之前構造.
鏈接:https://www.zhihu.com/question/27704562/answer/37760739
來源:知乎
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。
不使用編譯器擴展,不用C++11,不加鎖,也不使用原子操作的話,
那必須有個條件就是main函數執行之前程序必須是單線程的,不然真不行,再百度也不行,
如果有符合以上條件的單例類且不要求main之前是單線程的,請告訴我....然後:
這是2b單例:template<typename T>
class Im2Bsingleton
{
public:
static T* get() {
if(ptr==nullptr) { ptr=new T; }
return ptr;
};
private:
static T* ptr;
};
這個顯然不是線程安全的,因爲ptr會在第一次被使用的時候new T,
而第一次被使用的時候8成是main之後了,就很可能是多線程情況下,
那麼可以使用一個原子操作:template<typename T>
class AtomicSingleton
{
public:
static T* get() {
for(;;) {
T* k=nullptr;
ptr.compare_exchange_weak(k,(T*)&place_holder);
if(k==0){
k=new T;
ptr.set(k);//此處必然成功
return k;
} else if(k==(T*)&place_holder) {
//別的線程佔坑了,等着去吧!
} else {
return k;
}
}
};
private:
static atomic<T*> ptr;
static char place_holder;
};
代價是一次CAS和一次判斷,但是題目說了不能加鎖或者原子操作,那麼,
再說一遍就是必須保證main函數執行之前不能存在多線程情況,
可以這樣:template<typename T>
class Singleton
{
public:
static T* get() {
return ptr;
};
private:
static T* ptr;
};
你肯定覺得這個不對啊,ptr沒被構造啊,對,保證前提的基礎上,
我們只需要保證ptr能在main之前絕逼被構造就行了,
我們需要一個helper:普通單例
:template<typename T>
class Singleton
{
private:
struct ShitCleanHelper {
ShitCleanHelper(){ Singleton<T>::ptr=new T; }
~ShitCleanHelper(){
delete Singlenton<T>::ptr;
Singlenton<T>::ptr=nullptr;
}
};
public:
static T* get() {
return ptr;
};
private:
static T* ptr;
static ShitCleanHelper helper;
friend class ShitCleanHelper;
};
由於helper是static的,根據C++標準,靜態對象被保證在main之前構造,
因此ShitCleanHelper被絕對保證在main之前構造,它在構造時候不幹任何事,
只負責new T.這樣,當進入main之後,ptr肯定就指着正確的對象了.
你會說那又不對了,這樣雖然能夠保證Singlenton在main之前new,
但是萬一我需要在main之前就要用get()怎麼辦?
又或者,我的模塊與模塊之間不同的static對象互相有交互,這一切都發生在main之前,怎麼辦?
下面是文藝單例:template<typename T,bool DirectConstruction = true>
class ScopedSingleton
{
private:
class InstanceCreator {
public:
InstanceCreator() :m_ptr(new T) {};
~InstanceCreator() { delete m_ptr; };
operator T&() noexcept { return *m_ptr;}
private:
T* RESTRICT const m_ptr;
};
class DummyInstanceUser
{
public:
DummyInstanceUser(){ ScopedSingleton::getInstance(); }
~DummyInstanceUser() { ScopedSingleton::getInstance(); }
void DoNothing(){}
};
public:
static T& getInstance()
{
static InstanceCreator m_instancePtr;
m_dummyUser.DoNothing();
//此處似乎是模板的一個bug,加了這一句才能保證main之前m_instancePtr一定被構造.
return m_instancePtr;
};
private:
ScopedSingleton(){ getInstance(); }
~ScopedSingleton(){ getInstance(); }
private:
static DummyInstanceUser m_dummyUser;
};
首先,C++保證函數內的static變量保證在它第一次被使用之前被構造,
這就避免了"普通單例"中可能在ShitCleanHelper構造new T之前就要使用的尷尬局面.
無論什麼時候要用它,它肯定會在這之前被構造了,但是這樣一來又有問題了,
假如main函數之前誰都不用它,而main之後纔有人第一次用到它,
那還怎麼保證它在main之前被構造以避免多線程問題呢?
這就需要DummyInstanceUser出現了, DummyInstanceUser存在的唯一一個實例是m_dummyUser,
由於它是static的,所以它保證了在main函數之前被構造,而它的構造函數的唯一目的就是"假裝使用了m_instancePtr",
這樣即使沒有人再main之前調用getInstance(),DummyUser依然會作爲main之前的最後一道屏障保證在main之前一定構造成功.同時,這種單例還避免了跨模塊的static變量互相引用的問題,例如有不同的單例A和B,它們在兩個不同的cpp中,則它們的構造順序雖然都在main之前,但互相之間的順序是不確定的,但是使用這種文藝單例,如果A需要用到B,請看這一段文字的第一句話,只要A會用到B,B就會保證在A之前構造.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.