單例模式是一種創建型設計模式,其核心是確保一個類在程序中只能存在唯一實例,并提供一個全局訪問點。這種模式適用于需要集中管理資源(如日志、配置、連接池)的場景,避免資源沖突和重復創建的開銷。
一、介紹
類型
單例模式的核心是限制實例化,但根據實例創建時機的不同,分為餓漢式和懶漢式兩種實現方式:
1. 餓漢式(Eager Initialization)
- 特點:程序啟動時(類加載階段)就創建實例,無論是否使用。
- 優點:實現簡單,天然線程安全(C++中全局變量初始化在主線程執行)。
- 缺點:如果實例占用資源大且始終未被使用,會造成資源浪費。
2. 懶漢式(Lazy Initialization)
- 特點:第一次使用時才創建實例,延遲初始化。
- 優點:避免資源浪費,適合實例化成本高的場景。
- 缺點:基礎實現線程不安全,需要額外處理多線程同步問題。
選擇餓漢式還是懶漢式,需根據實例化成本、使用頻率和線程環境綜合判斷:資源占用小且必用→餓漢式;資源占用大或不一定使用→懶漢式。
單例模式的優點
- 唯一實例
- 確保類在整個程序中只有一個實例,避免資源沖突
- 全局訪問
- 提供統一的訪問點,方便在程序任何地方使用
- 資源控制
- 集中管理資源(如文件、網絡連接),避免重復創建銷毀
- 延遲初始化
- 只有在首次使用時才初始化,節省系統資源
- 線程安全(優化后)
- 現代實現可保證多線程環境下的安全性
單例模式的適用場景
- 全局資源管理器
- 日志管理器:確保所有日志寫入同一文件/系統
- 配置管理器:全局共享一份配置數據
- 連接池:數據庫/網絡連接的統一管理
- 設備訪問控制
- 打印機管理器:避免多個程序同時操作硬件
- 傳感器接口:確保數據讀取的一致性
- 全局狀態存儲
- 應用程序上下文:存儲全局狀態信息
- 緩存管理器:全局共享緩存數據
二、實現
以一個線程安全的日志管理器為例
#include <iostream>
#include <fstream>
#include <string>
#include <mutex>
#include <chrono>
#include <thread>
#include <sstream>// 單例模式:日志管理器
class Logger {
private:// 私有構造函數:防止外部實例化Logger() {logFile_.open("app.log", std::ios::app);if (!logFile_.is_open()) {throw std::runtime_error("無法打開日志文件");}log("Logger 初始化完成");}// 私有析構函數:防止外部銷毀~Logger() {if (logFile_.is_open()) {log("Logger 已關閉");logFile_.close();}}// 禁用拷貝構造和賦值運算符Logger(const Logger&) = delete;Logger& operator=(const Logger&) = delete;// 禁用移動構造和移動賦值Logger(Logger&&) = delete;Logger& operator=(Logger&&) = delete;// 日志文件流std::ofstream logFile_;// 線程安全互斥鎖mutable std::mutex mutex_;// 獲取當前時間字符串std::string getCurrentTime() const {auto now = std::chrono::system_clock::now();std::time_t time = std::chrono::system_clock::to_time_t(now);return std::ctime(&time);}public:// 全局訪問點:獲取唯一實例static Logger& getInstance() {// 局部靜態變量:C++11后保證線程安全初始化static Logger instance;return instance;}// 日志寫入函數void log(const std::string& message) const {std::lock_guard<std::mutex> lock(mutex_); // 保證線程安全if (logFile_.is_open()) {logFile_ << "[" << getCurrentTime() << "] " << message << std::endl;}// 同時輸出到控制臺std::cout << "[" << getCurrentTime() << "] " << message << std::endl;}
};// 測試多線程環境下單例的唯一性
void threadFunction(int threadId) {std::stringstream ss;ss << "線程 " << threadId << " 正在寫入日志";Logger::getInstance().log(ss.str());// 模擬工作std::this_thread::sleep_for(std::chrono::milliseconds(100));ss.clear();ss << "線程 " << threadId << " 完成工作";Logger::getInstance().log(ss.str());
}int main() {try {// 主線程日志Logger::getInstance().log("程序啟動");// 創建多個線程測試std::thread t1(threadFunction, 1);std::thread t2(threadFunction, 2);std::thread t3(threadFunction, 3);// 等待線程完成t1.join();t2.join();t3.join();Logger::getInstance().log("程序退出");} catch (const std::exception& e) {std::cerr << "錯誤: " << e.what() << std::endl;return 1;}return 0;
}
輸出結果(示例)
[Thu Aug 22 10:00:00 2024
] Logger 初始化完成
[Thu Aug 22 10:00:00 2024
] 程序啟動
[Thu Aug 22 10:00:00 2024
] 線程 1 正在寫入日志
[Thu Aug 22 10:00:00 2024
] 線程 2 正在寫入日志
[Thu Aug 22 10:00:00 2024
] 線程 3 正在寫入日志
[Thu Aug 22 10:00:00 2024
] 線程 1 完成工作
[Thu Aug 22 10:00:00 2024
] 線程 2 完成工作
[Thu Aug 22 10:00:00 2024
] 線程 3 完成工作
[Thu Aug 22 10:00:00 2024
] 程序退出
[Thu Aug 22 10:00:00 2024
] Logger 已關閉
應用場景
- 日志系統
- 整個應用使用同一個日志實例,確保日志順序和完整性
- 配置管理
- 讀取配置文件后,全局共享配置信息,避免重復IO操作
- 數據庫連接池
- 管理數據庫連接的創建和復用,防止連接數爆炸
- GUI應用
- 主窗口實例:確保應用程序只有一個主窗口
- 對話框管理器:統一管理對話框的創建和銷毀
- 硬件交互
- 如打印機、攝像頭等設備的訪問控制,避免沖突
三、優化
優化點
- 通用單例基類
- 使用CRTP(奇異遞歸模板模式)實現通用單例基類,避免重復代碼
- 派生類只需繼承
Singleton<Derived>
即可獲得單例特性
- 增強的線程安全
- 采用雙重檢查鎖定(DCLP)+ 原子變量,在保證線程安全的同時減少鎖競爭
- C++11及以上標準確保局部靜態變量初始化的線程安全性
- 資源管理優化
- 使用
std::unique_ptr
管理實例,確保自動釋放資源,避免內存泄漏 - 提供
destroyInstance()
方法,支持在測試場景下手動銷毀實例
- 使用
- 功能擴展
- 增加日志級別過濾,可動態設置日志輸出粒度
- 支持帶時間戳(精確到毫秒)和級別標記的日志格式
- 控制臺輸出支持彩色顯示,區分不同日志級別
- 可測試性提升
- 允許手動銷毀實例,支持單元測試中的狀態重置
- 清晰的接口設計便于模擬(Mock)測試
- 現代C++特性
- 使用
std::atomic
確保指針操作的原子性 - 采用
constexpr
和類型安全的枚舉類 - 使用
std::lock_guard
進行RAII風格的鎖管理
- 使用
#include <iostream>
#include <fstream>
#include <string>
#include <mutex>
#include <chrono>
#include <thread>
#include <sstream>
#include <memory>
#include <atomic>// 單例基類模板(CRTP模式:Curiously Recurring Template Pattern)
template <typename T>
class Singleton {
protected:// 允許派生類構造Singleton() = default;// 禁止拷貝和移動Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;Singleton(Singleton&&) = delete;Singleton& operator=(Singleton&&) = delete;public:// 全局訪問點 - 線程安全的延遲初始化static T& getInstance() {// 雙重檢查鎖定(DCLP)優化,減少鎖競爭if (!instance_.load(std::memory_order_acquire)) {std::lock_guard<std::mutex> lock(mutex_);if (!instance_.load(std::memory_order_relaxed)) {static std::unique_ptr<T> instance(new T());instance_.store(instance.get(), std::memory_order_release);}}return *instance_.load(std::memory_order_acquire);}// 手動銷毀實例(主要用于測試場景)static void destroyInstance() {std::lock_guard<std::mutex> lock(mutex_);if (instance_.load(std::memory_order_relaxed)) {instance_.store(nullptr, std::memory_order_release);}}protected:static std::atomic<T*> instance_; // 原子指針確保線程安全static std::mutex mutex_; // 互斥鎖
};// 初始化靜態成員
template <typename T>
std::atomic<T*> Singleton<T>::instance_(nullptr);template <typename T>
std::mutex Singleton<T>::mutex_;// 日志級別枚舉
enum class LogLevel {DEBUG,INFO,WARNING,ERROR
};// 具體單例類:日志管理器(繼承自單例基類)
class Logger : public Singleton<Logger> {// 允許基類訪問私有構造函數friend class Singleton<Logger>;private:std::ofstream logFile_;mutable std::mutex writeMutex_; // 日志寫入鎖LogLevel minLogLevel_; // 日志級別過濾// 私有構造函數 - 初始化日志系統Logger() : minLogLevel_(LogLevel::DEBUG) {logFile_.open("app.log", std::ios::app);if (!logFile_.is_open()) {throw std::runtime_error("無法打開日志文件");}log(LogLevel::INFO, "Logger 初始化完成");}// 日志級別轉字符串std::string levelToString(LogLevel level) const {switch (level) {case LogLevel::DEBUG: return "DEBUG";case LogLevel::INFO: return "INFO";case LogLevel::WARNING: return "WARNING";case LogLevel::ERROR: return "ERROR";default: return "UNKNOWN";}}// 獲取當前時間字符串std::string getCurrentTime() const {auto now = std::chrono::system_clock::now();auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(now.time_since_epoch() % std::chrono::seconds(1));std::time_t time = std::chrono::system_clock::to_time_t(now);std::tm tm = *std::localtime(&time);char buffer[32];std::strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm);std::stringstream ss;ss << buffer << "." << std::setw(3) << std::setfill('0') << ms.count();return ss.str();}public:// 設置日志級別過濾void setLogLevel(LogLevel level) {std::lock_guard<std::mutex> lock(writeMutex_);minLogLevel_ = level;}// 日志寫入函數(支持不同級別)void log(LogLevel level, const std::string& message) const {// 日志級別過濾if (level < minLogLevel_) {return;}std::lock_guard<std::mutex> lock(writeMutex_);std::string timeStr = getCurrentTime();std::string levelStr = levelToString(level);std::string logMessage = "[" + timeStr + "] [" + levelStr + "] " + message;// 寫入文件if (logFile_.is_open()) {logFile_ << logMessage << std::endl;}// 控制臺輸出(不同級別不同顏色)switch (level) {case LogLevel::ERROR:std::cerr << "\033[1;31m" << logMessage << "\033[0m" << std::endl;break;case LogLevel::WARNING:std::cout << "\033[1;33m" << logMessage << "\033[0m" << std::endl;break;case LogLevel::INFO:std::cout << "\033[1;32m" << logMessage << "\033[0m" << std::endl;break;default:std::cout << logMessage << std::endl;}}// 便捷日志函數void debug(const std::string& message) const { log(LogLevel::DEBUG, message); }void info(const std::string& message) const { log(LogLevel::INFO, message); }void warning(const std::string& message) const { log(LogLevel::WARNING, message); }void error(const std::string& message) const { log(LogLevel::ERROR, message); }
};// 測試多線程環境
void threadTask(int threadId) {Logger::getInstance().info("線程 " + std::to_string(threadId) + " 啟動");// 模擬工作std::this_thread::sleep_for(std::chrono::milliseconds(100));Logger::getInstance().debug("線程 " + std::to_string(threadId) + " 完成工作");
}int main() {try {// 基本使用示例Logger::getInstance().info("程序啟動");Logger::getInstance().setLogLevel(LogLevel::INFO); // 設置只顯示INFO及以上級別// 多線程測試std::thread t1(threadTask, 1);std::thread t2(threadTask, 2);std::thread t3(threadTask, 3);t1.join();t2.join();t3.join();// 測試不同日志級別Logger::getInstance().warning("這是一個警告");Logger::getInstance().error("這是一個錯誤");Logger::getInstance().debug("這個DEBUG日志不會顯示(因為日志級別設置)");Logger::getInstance().info("程序退出");// 手動銷毀(可選,主要用于測試)Logger::destroyInstance();} catch (const std::exception& e) {std::cerr << "錯誤: " << e.what() << std::endl;return 1;}return 0;
}
輸出結果(示例)
[2024-08-22 15:30:00.123] [INFO] Logger 初始化完成
[2024-08-22 15:30:00.125] [INFO] 程序啟動
[2024-08-22 15:30:00.126] [INFO] 線程 1 啟動
[2024-08-22 15:30:00.127] [INFO] 線程 2 啟動
[2024-08-22 15:30:00.128] [INFO] 線程 3 啟動
[2024-08-22 15:30:00.230] [WARNING] 這是一個警告
[2024-08-22 15:30:00.231] [ERROR] 這是一個錯誤
[2024-08-22 15:30:00.232] [INFO] 程序退出
優化后的優勢
- 更高的復用性
- 通用單例基類可被多個類復用,減少代碼冗余
- 更好的性能
- 雙重檢查鎖定減少了鎖競爭,提高多線程環境下的性能
- 更靈活的功能
- 日志級別過濾等擴展功能使單例類更實用
- 更安全的資源管理
- 智能指針和RAII確保資源正確釋放,避免內存泄漏
- 更好的可測試性
- 支持手動銷毀實例,便于單元測試和狀態重置