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之前構造.
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章