超全總結--單例模式

簡介

單例模式定義:確保一個類只有一個實例,並提供一個全局訪問點來訪問這個唯一實例。

單例模式有3個要點:

  • 這個類只能有一個實例;
  • 它必須自己創建這個實例;
  • 它必須自己向整個系統提供這個實例。

從具體實現角度來說,可分爲以下三點:

  • 提供一個 private 構造函數(防止外部調用而構造類的實例)
  • 提供一個該類的 static private 對象
  • 提供一個 static public 函數,用於創建或獲取其本身的靜態私有對象(例如:GetInstance())

除此之外,還有一些關鍵點(需要多加註意,很容易忽視):

  • 線程安全(雙檢鎖 - DCL,即:double-checked locking)
  • 資源釋放

image

分類

我們將默認的構造函數聲明爲私有的,這樣就不會被外部所new了,甚至可以將析構函數也聲明爲私有的,這樣就只有自己能夠刪除自己了。單例模式非常好實現,直接就可以在靜態區初始化instance,然後通過getInstance返回,這種就被稱爲餓漢式單例類。也有些寫法是在getInstance中new instance然後返回,這種就被稱爲懶漢式單例類,但這涉及到第一次getInstance的一個判斷問題

單例大約有兩種實現方法:懶漢與餓漢。

懶漢:故名思義,不到萬不得已就不會去實例化類,也就是說在第一次用到類實例的時候纔會去實例化;

餓漢:餓了肯定要飢不擇食。所以在單例類定義的時候就進行實例化。

餓漢式(局部靜態變量)

這種方式實現非常簡單,而且無需擔心單例的銷燬問題。

// singleton.h
#ifndef SINGLETON_H
#define SINGLETON_H

// 非真正意義上的單例
class Singleton
{
public:
    static Singleton& GetInstance()
    {
        static Singleton instance;
        return instance;
    }

private:
    Singleton() {}
};

#endif // SINGLETON_H

但是,這並非真正意義上的單例。當使用如下方式訪問單例時:

Singleton single = Singleton::GetInstance();

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

爲了避免這個問題,有兩種解決方式:

  • 將 GetInstance() 函數的返回類型修改爲指針,而非引用。
  • 顯式地聲明類的拷貝構造函數,並重載賦值運算符。
    對於第一種方式,只需要修改 GetInstance() 的返回類型即可:
// singleton.h
#ifndef SINGLETON_H
#define SINGLETON_H

// 單例
class Singleton
{
public:
    // 修改返回類型爲指針類型
    static Singleton* GetInstance()
    {
        static Singleton instance;
        return &instance;
    }

private:
    Singleton() {}
};

#endif // SINGLETON_H

既然編譯器會生成一個默認的拷貝構造函數,那麼,爲什麼不讓編譯器不這麼幹呢?這就產生了第二種方式:

// singleton.h
#ifndef SINGLETON_H
#define SINGLETON_H

#include <iostream>

using namespace std;

// 單例
class Singleton
{
public:
    static Singleton& GetInstance()
    {
        static Singleton instance;
        return instance;
    }

    void doSomething() {
        cout << "Do something" << endl;
    }

private:
    Singleton() {}  // 構造函數(被保護)
    Singleton(Singleton const &);  // 無需實現
    Singleton& operator = (const Singleton &);  // 無需實現
};

#endif // SINGLETON_H

這樣以來,既可以保證只存在一個實例,又不用考慮內存回收的問題。

Singleton::GetInstance().doSomething();  // OK
Singleton single = Singleton::GetInstance();  // Error 不能編譯通過

懶漢式(創建對象指針)

在懶漢式下,如果使用多線程,會出現線程安全隱患。爲了解決這個問題,可以引入雙檢鎖 - DCL 機制。

// singleton.h
#ifndef SINGLETON_H
#define SINGLETON_H

#include <iostream>
#include <mutex>
using namespace std;

// 單例 - 懶漢式/餓漢式公用
class Singleton
{
public:
    static Singleton* GetInstance();

private:
    Singleton() {}  // 構造函數(被保護)

private:
    static Singleton *m_pSingleton;  // 指向單例對象的指針
    static mutex m_mutex;  // 鎖
};

#endif // SINGLETON_H
// singleton.cpp
#include "singleton.h"

// 單例 - 懶漢式(雙檢鎖 DCL 機制)
Singleton *Singleton::m_pSingleton = NULL;
mutex Singleton::m_mutex;

Singleton *Singleton::GetInstance()
{
    if (m_pSingleton == NULL) {
        std::lock_guard<std::mutex> lock(m_mutex);  // 自解鎖
        if (m_pSingleton == NULL) {
            m_pSingleton = new Singleton();
        }
    }
    return m_pSingleton;
}

資源釋放:

// 單例 - 主動釋放
static void DestoryInstance()
{
    if (m_pSingleton != NULL) {
        delete m_pSingleton;
        m_pSingleton = NULL;
    }
}

然後在需要釋放的時候,手動調用該接口:

Singleton::GetInstance()->DestoryInstance();

4.單例模式總結

懶漢式的特點:

非多線程安全

優點:第一次調用才初始化,避免內存浪費。
缺點:必須加鎖(在“線程安全”部分分享如何加鎖)才能保證單例,但加鎖會影響效率。

餓漢式的特點:

多線程安全

優點:沒有加鎖,執行效率會提高。
缺點:類加載時就初始化,浪費內存。

整體優點:

  • 單例模式提供了嚴格的對唯一實例的創建和訪問
  • 單例模式的實現可以節省系統資源

整體缺點:

  • 如果某個實例負責多重職責但又必須實例唯一,那單例類的職責過多,這違背了單一職責原則
  • 多線程下需要考慮線程安全機制
  • 單例模式沒有抽象層,不方便擴展

適用環境:

系統只需要一個實例對象
某個實例只允許有一個訪問接口

常用的場景

單例模式常常與工廠模式結合使用,因爲工廠只需要創建產品實例就可以了,在多線程的環境下也不會造成任何的衝突,因此只需要一個工廠實例就可以了。
優點
1.減少了時間和空間的開銷(new實例的開銷)。
2.提高了封裝性,使得外部不易改動實例。
缺點
1.懶漢式是以時間換空間的方式。
2.餓漢式是以空間換時間的方式。

C++實現代碼
#ifndef _SINGLETON_H_
#define _SINGLETON_H_
class Singleton{
public:
static Singleton* getInstance();
private:
Singleton();
//把複製構造函數和=操作符也設爲私有,防止被複制
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
static Singleton* instance;
};
#endif
#include "Singleton.h"
Singleton::Singleton(){
}
Singleton::Singleton(const Singleton&){
}
Singleton& Singleton::operator=(const Singleton&){
}
//在此處初始化
Singleton* Singleton::instance = new Singleton();
Singleton* Singleton::getInstance(){
return instance;
}
 
#include "Singleton.h"
#include <stdio.h>
int main(){
Singleton* singleton1 = Singleton::getInstance();
Singleton* singleton2 = Singleton::getInstance();
if (singleton1 == singleton2)
fprintf(stderr,"singleton1 = singleton2\n");
return 0;



}

結果輸出:singleton1 = singleton2
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章