單例模式是最廣為人知的設計模式之一,旨在確保一個類僅有一個實例並提供全局訪問點。本文從 larbin 專案中的全局結構體切入,層層遞進至經典的 C++ 單例實現,並探討資源釋放與線程安全問題。
早期 C 風格常用結構體 + 靜態成員模擬單例:
// global.h
struct Global {
Global();
static int a;
// ...
};
// global.cc
int Global::a = 0; // 靜態成員需類外定義
// main.cc 僅創建一個實例使用
缺點顯而易見:無構造函數私有化保護、無法控制初始化時機、銷毀順序不可控。
C++ 單例通過私有構造函數、靜態實例指針、公有靜態獲取方法實現:
class Singleton {
public:
static Singleton* GetInstance() {
if (m_pInstance == nullptr)
m_pInstance = new Singleton();
return m_pInstance;
}
private:
Singleton() {}
static Singleton* m_pInstance;
// 內嵌垃圾回收類,確保程序結束時自動釋放
class Garbo {
public:
~Garbo() {
if (Singleton::m_pInstance)
delete Singleton::m_pInstance;
}
};
static Garbo garbo; // 靜態成員,程序結束時析構
};
Singleton* Singleton::m_pInstance = nullptr;
Singleton::Garbo Singleton::garbo;
關鍵點:
private 防止外部實例化m_pInstance 持有唯一實例Garbo 的靜態實例 garbo 利用程序結束時自動析構特性釋放記憶體上述懶漢式在多線程下不安全。C++11 起可用 std::call_once 或局部靜態變數(Magic Static)實現線程安全:
class Singleton {
public:
static Singleton& GetInstance() {
static Singleton instance; // C++11 保證線程安全初始化
return instance;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
~Singleton() = default;
};
此寫法無需手動管理記憶體、無需 Garbo、天然線程安全、銷毀順序由編譯器保證。
典型場景:日誌系統、配置管理、資源池、GUI 窗口管理器等。但濫用單例會導致全局狀態難測試、耦合度高、隱藏依賴。現代 C++ 傾向於依賴注入替代單例。
從 C 風格全局結構到現代 C++ 線程安全單例,核心演進在於:封裝構造、控制生命週期、保證線程安全。根據項目標準選擇合適版本,切記「單例不等於全局變數」,謹慎使用。