C++的單例模式


單例模式可能是使用最廣泛的設計模式,其意圖是保證一個類僅有一個實例,並提供一個訪問它的全局訪問點。

單例模式有很多實現方法,在C++中,甚至可以直接用一個全局變量做到這一點,但是這樣的代碼顯得很不優雅。《設計模式》一書中給出了一種很不錯的實現,定義一個單例類,使用類的私有靜態指針變量指向類的唯一實例,並用一個公有的靜態方法獲得該實例。

class CSingleton
{
private:
    CSingleton()   //構造函數是私有的
    {
    }
    static CSingleton *m_pInstance;
public:
    static CSingleton * GetInstance()
    {
        if(m_pInstance == NULL)  //判斷是否第一次調用
            m_pInstance = new CSingleton();
        return m_pInstance;
    }
};

用戶訪問唯一實例的方法只有GetInstance()成員函數,如果不通過這個函數,任何創建實例的嘗試都會失敗,因為類的構造函數是私有的。GetInstance()使用懶惰初始化,也就是說它的返回值只有在這個函數首次被訪問時被創建。這是一種防彈設計——所有GetInstance()之后的調用都返回相同的實例指針:

CSingleton* p1 = CSingleton :: GetInstance();
CSingleton* p2 = p1->GetInstance();
CSingleton & ref = * CSingleton :: GetInstance();
對GetInstance稍加修改,這個設計模板便可以適用於可變多實例情況,如一個類允許最多五個實例。

 

有經驗的讀者可能會問,m_pInstance指向的空間什么時候釋放?更嚴重的問題是,該實例的析構函數什么時候調用?
可以在程序結束時調用GetInstance(),並對返回的指針調用delete操作。這樣這可以實現功能,但不僅丑陋,而且容易出錯。

一個妥善的辦法是讓這個類自己知道在合適的時候自己刪除,或者說把刪除自己的操作掛在操作系統某個合適的點上。我們知道,程序在結束的時候,系統會析構所有的全局變量。事實上,系統也會析構所有類的靜態成員變量,就像這些靜態成員也是全局變量一樣。利用這個特性,我們可以在單例類中定義這樣一個靜態成員變量,而它的唯一工作就是在析構函數中刪除單例類的實例。如下面代碼中的CGarbo類。

class CSingleton
{
private:
    CSingleton()
    {
    }
    static CSingleton *m_pInstance;
    class CGarbo   //它的唯一工作就是在析構函數中刪除CSingleton的實例
    {
    public:
        ~CGarbo()
        {
            if(CSingleton::m_pInstance)
                delete CSingleton::m_pInstance;
        }
    };
    static CGarbo Garbo;  //定義一個靜態成員變量,程序結束時,系統會自動調用它的析構函數
public:
    static CSingleton * GetInstance()
    {
        if(m_pInstance == NULL)  //判斷是否第一次調用
            m_pInstance = new CSingleton();
        return m_pInstance;
    }
};

類CGarbo被定義為CSingleton的私有嵌套類,以防止該類在其他地方被濫用。程序運行結束時,系統會調用CSingleton的靜態成員Garbo的析構函數,該析構函數會刪除單例的唯一實例。

 

進一步討論,

但是添加一個類的靜態對象,總是讓人不太滿意,所以有人用如下方法來重新實現單例和解決它相應的問題,代碼如下:

class CSingleton
{
private:
    CSingleton()   //構造函數是私有的
    {
    }
public:
    static CSingleton & GetInstance()
    {
        static CSingleton instance;   //局部靜態變量
        return instance;
    }
};

使用局部靜態變量,非常強大的方法,完全實現了單例的特性,而且代碼量更少,也不用擔心單例銷毀問題。

但使用此種方法也會出現問題,當如下方法使用單例時問題來了:

Singleton singleton = Singleton :: GetInstance();

這么做就出現了一個類拷貝的問題,這就違背了單例的特性。產生這個問題原因在於:編譯器會為類生成一個默認的構造函數,來支持類的拷貝。

最后沒有辦法,我們要禁止類拷貝和類賦值,禁止程序員用這種方法來使用單例,比如返回一個指針

class CSingleton
{
private:
    CSingleton()   //構造函數是私有的
    {
    }
public:
    static CSingleton * GetInstance()
    {
        static CSingleton instance;   //局部靜態變量
        return &instance;
    }
};

可以直接讓編譯器不這么干嗎?這時我才想起可以顯示的聲明類拷貝的構造函數,和重載 = 操作符,新的單例類如下:

class CSingleton
{
private:
    CSingleton()   //構造函數是私有的
    {
    }
    CSingleton(const CSingleton &);
    CSingleton & operator = (const CSingleton &);
public:
    static CSingleton & GetInstance()
    {
        static CSingleton instance;   //局部靜態變量
        return instance;
    }
};

關於Singleton(const Singleton &);和 Singleton & operate = (const Singleton&);函數,需要聲明成私有的,並且只聲明不實現。這樣,如果用上面的方式來使用單例時,不管是在友元類中還是其他的,編譯器都是報錯。

 

考慮到線程安全、異常安全,可以做以下擴展:

class Lock
{
private:       
    CCriticalSection m_cs;
public:
    Lock(CCriticalSection  cs) : m_cs(cs)
    {
        m_cs.Lock();
    }
    ~Lock()
    {
        m_cs.Unlock();
    }
};
 
class Singleton
{
private:
    Singleton();
    Singleton(const Singleton &);
    Singleton& operator = (const Singleton &);
 
public:
    static Singleton *Instantialize();
    static Singleton *pInstance;
    static CCriticalSection cs;
};
 
Singleton* Singleton::pInstance = 0;
 
Singleton* Singleton::Instantialize()
{
    if(pInstance == NULL)
    {   //double check
        Lock lock(cs);           //用lock實現線程安全,用資源管理類,實現異常安全
        //使用資源管理類,在拋出異常的時候,資源管理類對象會被析構,析構總是發生的無論是因為異常拋出還是語句塊結束。
        if(pInstance == NULL)
        {
            pInstance = new Singleton();
        }
    }
    return pInstance;
}

之所以在Instantialize函數里面對pInstance 是否為空做了兩次判斷,因為該方法調用一次就產生了對象,pInstance == NULL 大部分情況下都為false,如果按照原來的方法,每次獲取實例都需要加鎖,效率太低。而改進的方法只需要在第一次 調用的時候加鎖,可大大提高效率。


————————————————
版權聲明:本文為CSDN博主「hackbuteer1」的原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/Hackbuteer1/article/details/7460019


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM