1. 程式人生 > >C++ (設計模式)實現單例模式

C++ (設計模式)實現單例模式

設計模式

從實踐的角度來看,設計模式有兩個重要的屬性。首先,它們描述了經過驗證的、成功的設計技術,這些技術可以按上下文相關的方式進行定製,一遍滿足新的設計場合的要求。其次,並且可能更重要的是,在提及某個特定模式的應用時不僅包括其中用到的技術,還包括應用該模式的動因以及應用後所達到的效果。

設計模式包括4個不可缺少的部分:

首先,設計模式必須具有一個毫無歧義的名字。使用精確的模式名字比使用不那麼精確的名字具有更明確的優勢。

其次,對模式描述時必須定義該模式所能解決的問題

再次,對模式描述時要記述該問題的解決方案。

最後,對模式描述時要記述將該模式應用於某個上下文的後果。

單例模式實現及優化

單例模式其實就是一種軟體設計模式,一個物件只能被初始化一次

設計模式經典GoF定義的單例模式需要滿足以下兩個條件:

保證一個類只建立一個例項

提供對該例項的全域性訪問點

如果系統有類似的實體(有且只有一個,且需要全域性訪問),那麼就可以將其實現為一個單例。實際工作中常見的應用舉例:

1)日誌類,一個應用往往只對應一個日誌例項。

2)配置類,應用的配置集中管理,並提供全域性訪問。

3)管理器,比如windows系統的工作管理員就是一個例子,總是隻有一個管理器的例項。

4)共享資源類,載入資源需要較長時間,使用單例可以避免重複載入資源,並被多個地方共享訪問。
 

Lazy Singleton 懶漢模式

 

Singleton* Singleton::ps = NULL;
class Singleton
{
public:
    static Singleton& Instance()    //Instance()作為靜態成員函式提供的全域性訪問點
    {
        if(ps == nullptr)    //如果還未例項化,即可例項化,反之提供例項的引用
            ps = new Singleton;
        return *ps;    //返回指標的話可能誤被 delete, 返回引用較為安全
    }
private:
    Singleton();    //這裡將構造、析構、拷貝構造、賦值函式設為私有,杜絕了生成了新例
    ~Singleton();
    Singleton(const SIngleton& );
    Singleton& operator(const Single& );
    static Singleton* ps;
};
Singleton* Singleton::ps = nullptr;

這種方法的好處在於直到 Instance() 被訪問,才會生成例項,這種特性被稱為延遲初始化(Lazy Initialization),這在一些初始化時消耗較大的情況有很大優勢。

Lazy Singleton不是執行緒安全的,比如現在有執行緒A和執行緒B,都通過了 ps == nullptr 的判斷,那麼執行緒A和B都會建立新例項。單例模式保證生成唯一例項的規則被打破了

Eager Singleton 餓漢模式

這種實現在編譯器初始化的時候就完成了例項的建立,和上述的Lazy Singleton相反。

class Singleton
{
public:
    static Singleton& Instance()    //Instance() 作為靜態成員提供全域性訪問點
    {
        return instance;
    }
private:
    Singleton();    //將構造、賦值、析構、拷貝建構函式設為私有杜絕了其產生私有的例項
    ~Singleton();
    Singleton(const Singleton& );
    Singleton& operator = (const Singleton& );
    static Singleton instance;
};
Singleton Singleton::instance;

由於例項化是在初始化階段執行的,所以沒有執行緒安全的問題,但是潛在問題在於no-local static物件(函式外的static物件)在不同編譯單元(可理解為cpp檔案和其包含的標頭檔案)中的初始化順序是未定義的。如果在初始化完成之前呼叫 Instance()方法會返回一個未定義的例項。例如有兩個單例 SingletonA 和 SingletonB ,都採用了 Eager Initialization ,那麼如果 SingletonA 的初始化需要 SingletonB ,而這兩個單例又在不同的編譯單元,初始化順序是不定的,如果 SingletonA 在 SingletonB 之前初始化,就會出錯

Meyers Singleton

為了解決上面的問題,Scott Meyers在《Effective C++》(Item 04)中的提出另一種更優雅的單例模式實現,使用local static物件(函式內的static物件)。當第一次訪問 Instance() 方法時才建立例項

class Singleton
{
public:
    static Singleton& Instance()    //Instance() 作為靜態成員函式提供全域性訪問點
    {
        static Singleton instance;
        return instance;
    }
private:
    Singleton();    //這裡將構造、拷貝構造、析構、賦值函式設為私有杜絕其生成新例
    ~Singleton();
    Singleton(const Singleton& );
    Singleton& operator = (const Singleton& );
};

C++0x之後該實現是執行緒安全的,有興趣可以讀相關的標準草案(section 6.7),編譯器的支援程度不一定,但是G++4.0及以上是支援的。

Double-Checked Locking Pattern(雙檢測鎖模式)

回顧 Lazy Singleton 模式,考慮到執行緒安全,我們可以通過加鎖來保護單例初始化這一過程,雙檢測鎖模式就是在懶漢模式的基礎上稍作修改得到:

class Singleton
{
public:
    static Singleton& Instance()    //Instance()作為靜態成員函式提供裡全域性訪問點
    {
        if(ps == NULL)
        {
            Lock();    //上鎖
            if(ps == NULL)    //如果還未例項化,即可例項話,反之提供例項的引用
            ps = new Singleton;
            Unlock();    //解鎖
        }
        return *ps;    //返回指標的話可能會誤被 delete,返回引用安全一點
    }
private:
    Singleton();    //這裡將構造,析構,拷貝構造,賦值函式設為私有,杜絕了生成新
    ~Singleton();
    Singleton(const Singleton&);
    Singleton& operator=(const Singleton&);
    static Singleton* ps;
};
Singleton* Singleton::ps = NULL;

以上的上鎖和解鎖僅用於說明,實際應用中可以使用互斥鎖,單一訊號量等方法去實現。這裡的兩次 ps == NULL,是借鑑了Java的單例模式實現時,使用的所謂的“雙檢鎖模式”(Double-Checked Locking Pattern)。因為進行一次加鎖和解鎖是需要付出對應的代價的,而進行兩次判斷,就可以避免多次加鎖與解鎖操作,同時也保證了執行緒安全。理論上問題解決了,但是在實踐中有很多坑,如指令重排、多核處理器等問題讓DCLP實現起來比較複雜比如需要使用記憶體屏障,詳細的分析可以閱讀這篇論文。

在C++11中有全新的記憶體模型和原子庫,可以很方便的用來實現DCLP。這裡不展開。有興趣可以閱讀這篇文章《DoubleChecked Locking is Fixed In C++11》。

pthread_once

在多執行緒程式設計環境下,儘管 pthread_once() 呼叫會出現在多個執行緒中,init_routine()函式僅執行一次,pthread_once是很適合用來實現執行緒安全單例。(pthread_once 在一個程序裡只會執行一次,其實現方式使用的就是互斥鎖+條件變數的方法)

pthread_once_t once = PTHREAD_ONCE_INIT;
class Singleton
{
public:
    static Singleton& Instance()    //Instance()作為靜態成員函式提供一次例項化以及全域性訪問點
    {
        pthread_once(&once, &Init);
        return *ps;
    }
    static void Init()
    {
        ps = new Singleton;
    }
private:
    Singleton();
    ~Singleton();
    Singleton(const Singleton&);
    Singleton& operator=(const Singleton&);
    static Singleton* ps;
};
Singleton* Singleton::ps = NULL;

總結:

單例模式的實現方法很多,要完成一個完美的實現很難,程式碼也會很複雜,但是掌握基礎的實現還是很必要的。此外還要在實際應用中不斷地去優化和探索。除了執行緒安全,一些場景下還有需要考慮資源釋放,生命週期等相關問題,可以參見《Modern C++ Design》中對Singleton的討論。