【設計模式】C++單例模式的幾種寫法——Java自動加載內部類對象,C++怎麼破?

單例模式是最簡單的設計模式,就讓我像玩簡單的遊戲同樣寫下去吧。程序員

v1: 簡單模式設計模式

和這個版本有過一面之緣,但不敢苟同。安全

class Singleton 
{
    private:
    Singleton() {}
    public:
    static Singleton * getIns()
    {
        static Singleton * ins = new Singleton();
        return ins;
    }
};

問題:什麼時候析構不明確;最重要的是調用屢次getIns函數會產生多個static Singleton指針,指向每次都調用都new出來的實例。多線程

 

v2: 通常模式函數

典型寫法學習

class Singleton 
{
    private:
    Singleton() {}
    static Singleton * ins; 
    public:
    static Singleton * getIns()
    {
        if(!ins) ins  = new Singleton();
        return ins;
    }
};

static Singleton * Singleton::ins = NULL;

問題:仍然未考慮析構問題;對象可能被複製出多個副本。測試

Java中因爲容許在調用構造函數以前先初始化變量,所以有這樣一種寫法:spa

public class Singleton 
{
    private Singleton() {}
    public static Singleton ins = new Singleton(); 
    public static Singleton * getIns()
    {
        return ins;
    }
};

簡潔明瞭,也是蠻OK啦,析構也省了,而且因爲初始化這個語句是JVM作的,所以人工的同步也省了(不帶這麼欺負C++程序員的 = =)。線程

 

v3: 增強模式設計

加入私有的複製構造函數以防出現單例對象的副本;加入一個內部靜態類,整個程序結束後,靜態類隨着其餘靜態變量消亡,此時調用析構函數將ins析構。

class Singleton 
{
    private:
    Singleton() {}
    Singleton(const Singleton & s) {}
    Singleton & operator = (const Singleton & s) {}

    static Singleton * ins; 

    public:
    static Singleton * getIns()
    {
        if(!ins) ins  = new Singleton();
        return ins;
    }

    class CGarbo  // 內部類 
    {  
    public:  
        ~CGarbo()  
        {  
            if(Singleton::ins)  
                delete Singleton::ins;  
        }  
    };  
    static CGarbo Garbo; 
}; 
static Singleton * Singleton::ins = NULL;

問題:不是線程安全的。

 

v4: hard模式

    static Singleton * getIns()
    {
        pthread_mutex_lock( &mutex_lock );
        if(!ins) ins  = new Singleton();
        pthread_mutex_unlock( &mutex_lock );
        return ins;
    }

問題:無論ins是否是空,都要加鎖,代價高

 

v5: 再接再勵模式

當ins不爲空時,反正getIns函數無論讀寫,直接返回就行了,讀寫鎖儘可交給使用者。

    static Singleton * getIns()
    {
        if(!ins)
        {
            pthread_mutex_lock( &mutex_lock );
            if(!ins) ins = new Singleton();
            pthread_mutex_unlock( &mutex_lock );
        }
        return ins;
    }

天然,getIns加了鎖,析構的時候也一樣要加鎖。繼續學習,再接再勵!

 

v6:虎牢關模式

實際上,只有在第一次建立單例的時候須要得到鎖。既然能夠用一個內部類的靜態實例實現單例對象的自動釋放,何不把該靜態對象的初始化也交由該內部類來維護呢:

class Singleton
{
public:
    Singleton() {}
    Singleton(const Singleton &s) {}
    Singleton & operator = (const Singleton & s) {}

public:
    static Singleton *getInstance()
    {
        return GC::pinstance;
    }

    class GC
    {
    public:
        static Singleton *pinstance;
        GC()
        {
            pinstance = new Singleton();
        }
        ~GC()
        {
            if( pinstance )
            {
                    delete pinstance; 
            }
        }
    };
    GC gc;
};
Singleton * Singleton::GC::pinstance = nullptr;

注意,此處將Singleton的構造函數改爲了public。由於在系統中沒有Singleton實例時,Singleton的static成員也是不會被建立的,所以需將構造函數改爲public,在使用詞單例類時,先聲明一個Singleton對象。

如此,進程啓動,在初始化階段先初始化了不屬於任何實例對象的Singleton的各個靜態成員,順便new好了咱們所需的單例對象指針,這一過程不是線程來乾的,所以這樣的new操做是安全的。而在程序運行結束時,gc自動析構,將單例對象釋放掉,這一過程也不是線程來乾的,所以也是線程安全的,そして,至於pthread_mutex? WTF。而因爲無論存在多少Singleton對象,每次getInstance必然獲取到的是同一個instance。

 

v7:  終極模式

等等,全部「非你莫屬」的類,都要改寫成單例模式麼?NONONO,能不能只寫一個單例模式,全部須要限制實例個數的類均可複用之?這樣,當系統中須要多個不一樣類的單例對象時,能夠節約許多代碼。固然能夠作到,用模板就能夠:

 

template <typename T>
class Singleton
{
public:
    Singleton() {}
    Singleton(const Singleton &s) {}
    Singleton & operator = (const Singleton & s) {}

public:
    static Singleton *getInstance()
    {
        return GC::pinstance;
    }

    class GC
    {
    public:
        static T *pinstance;
        GC()
        {
            pinstance = new T();
        }
        ~GC()
        {
            if( pinstance )
            {
                    delete pinstance; pinstance = nullptr;
            }
        }
    };
    GC gc;
};
template <typename T> T
* Singleton<T>::GC::pinstance = nullptr;

    固然很顯然,使用了模板的話,就須要一些約定俗成的東西了,例如在實例化的時候須要一個不帶參數的構造函數T()。在使用時,對於用Singleton<T>::getInstance函數返回獲得的對象指針,就是非你莫屬的同一個指針了:

int main()
{
Singleton<int> sgton;
int *p1 = Singleton<int>::getInstance(); int *p2 = Singleton<int>::getInstance(); int *p3 = Singleton<int>::getInstance();
printf("%x\n", p1); cout
<<(p1==p2)<<endl; cout <<(p2==p3)<<endl; return 0; }

   以上測試代碼的結果爲0x........ 1 1.  

 

   ちょっと待って!!還不夠!以上代碼仍是有問題的!注意此時咱們要限制對象個數的不是Singleton對象了,而是T對象。所以設計目標是:無論申明多少個Singleton對象都是合法的,而無論申明瞭多少個Singleton對象,調用getInstance都只會獲得一個T對象。

    再看上面的代碼,若是用戶手賤聲明瞭多個Singleton對象會怎麼樣呢?對於一個Singleton類的static對象,只要程序中的Singleton對象個數大於0,就會維護惟一的一個static變量。但是上面的static變量是個指針!!這就意味着每次構造Singleton對象時,都會調用GC構造函數中的new運算符,將一個新的T對象的地址賦值給pinstance!!那舊的那個呢?WHO CARES!!這樣一來輕則致使內存泄漏,重則致使系統運行出錯——若是先申明瞭一個Singleton對象,用着用着中間又聲明出來一個Singleton對象,那麼pinstance指向的內容就被偷換了!

    說了這麼多,爲了防止發生這樣的事情,只須要在new的時候和delete的時候稍稍注意一下便可,爲了防止new兩次,加上if語句;爲了防止delete兩次(固然在其餘場合也同樣),養成delete以後就將指針賦爲空,以避免指針在其餘地方被誤引用。

template <typename T>
class Singleton
{
public:
    Singleton() {}
    Singleton(const Singleton &s) {}
    Singleton & operator = (const Singleton & s) {}

public:
    static T *getInstance()
    {
        return GC::pinstance;
    }

    class GC
    {
    public:
        static T *pinstance;
        GC()
        {
                if( !pinstance ) pinstance = new T();
        }
        ~GC()
        {
            if( pinstance )
            {
                    delete pinstance; pinstance = nullptr;
} } };
static GC gc; }; template <typename T> T * Singleton<T>::GC::pinstance = nullptr;

 這樣一來,只要在起子線程以前聲明一個Singleton對象,那麼無論後面是main線程仍是子線程聲明瞭其餘的Singleton對象,T對象都不會出現第二個。

 

反思:

1  爲何Java用內部類維護單例?Java這麼作是爲了在不寫synchronized關鍵字(減小鎖的代價)的狀況下,採用延時加載的方式實現單例。怎麼作到不用鎖而作到線程安全呢?在Java中,內部類都只會加載一次,所以內部類的初始化過程有且僅有一次,而這個過程是JVM乾的,不受用戶多線程的影響。爲何是延時加載呢?只有getInstance()用到了內部類的時候,纔會加載內部類。

2  爲何C++寫法和Java延時加載且線程安全的單例寫法不同呢?C++是不會加載內部類的,只有當有對象須要實例化時,才調用其構造函數進行初始化,同時也初始化內部類的static變量。若是像Java同樣只是定義一個內部類而不去實例化它,是不行的。所以按照上面的寫法,使用該單例模板類必須先定義一個Singleton對象,這時候因爲內部類做爲Singleton的成員,會先被構造,「順便地」就正確地new出了一個T對象,pinstance指針是static變量,一個類最多隻有一個,因此不會有兩個pinstance,那麼只要再內部類的構造中防止new出兩個T對象,就能夠保證進程中有惟一的pinstance指向惟一的T。

 

to be continue

相關文章
相關標籤/搜索