單例模式
- 一、核心原理
- 二、常見的單例模式實現方式
- 1. 懶漢式(Lazy Initialization)
- 2. 餓漢式(Eager Initialization)
- 三、關鍵實現細節解析
- 四、單例模式的適用場景與特點
- 使用場景
- 日志工具(確保日志寫入的唯一性)。
- spdlog第三方庫實現
- 使用步驟
- 特點
單例模式是一種常用的設計模式,其核心是確保一個類在全局只有唯一實例,并提供一個全局訪問點。
一、核心原理
- 限制實例化:通過私有化類的構造函數、拷貝構造函數和賦值運算符,禁止外部直接創建實例或復制實例。
- 唯一實例:在類內部維護一個靜態的自身實例指針,確保全局只有一個實例。
- 全局訪問:提供一個靜態的公開接口(如
getInstance()
),讓外部通過該接口獲取唯一實例。
二、常見的單例模式實現方式
1. 懶漢式(Lazy Initialization)
實例在第一次被使用時才創建(延遲初始化),節省資源。
#include <QMutex>
#include <QScopedPointer>class Singleton {
public:// 全局訪問點:獲取唯一實例static Singleton& getInstance() {// 雙重檢查鎖定(DCLP),避免多線程下重復創建if (m_instance.isNull()) {QMutexLocker locker(&m_mutex); // 加鎖,確保線程安全if (m_instance.isNull()) {m_instance.reset(new Singleton()); // 首次調用時創建實例}}return *m_instance;}// 示例:單例提供的功能方法void doSomething() {// ... 業務邏輯 ...}private:// 私有化構造函數:禁止外部創建實例Singleton() {}// 私有化拷貝構造和賦值運算符:禁止復制Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;// 靜態成員:存儲唯一實例static QScopedPointer<Singleton> m_instance;static QMutex m_mutex; // 互斥鎖,確保多線程安全
};// 初始化靜態成員(類外定義)
QScopedPointer<Singleton> Singleton::m_instance(nullptr);
QMutex Singleton::m_mutex;
2. 餓漢式(Eager Initialization)
實例在程序啟動時(類加載時)就創建,避免多線程同步問題,但可能提前占用資源。
class Singleton {
public:// 全局訪問點:直接返回預創建的實例static Singleton& getInstance() {static Singleton instance; // 靜態局部變量,程序啟動時初始化return instance;}void doSomething() {// ... 業務邏輯 ...}private:// 私有化構造函數Singleton() {}// 禁止復制Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;
};
三、關鍵實現細節解析
-
私有化構造函數
private
權限的構造函數阻止外部通過new Singleton()
或Singleton obj
創建實例,確保實例只能在類內部創建。 -
禁止復制和賦值
通過= delete
顯式刪除拷貝構造函數和賦值運算符,避免外部通過Singleton obj = Singleton::getInstance()
復制實例,保證唯一性。 -
靜態實例與全局訪問
類內部的靜態成員(m_instance
或靜態局部變量)存儲唯一實例,getInstance()
靜態方法提供全局訪問入口,確保任何地方都能獲取同一個實例。 -
線程安全處理
- 懶漢式中使用
QMutex
加鎖,避免多線程同時調用getInstance()
時創建多個實例(雙重檢查鎖定進一步優化性能)。 - 餓漢式依賴靜態變量的初始化特性(C++11 后靜態局部變量初始化是線程安全的),無需額外加鎖。
- 懶漢式中使用
四、單例模式的適用場景與特點
使用場景
- 全局配置管理(如程序的配置類)。
- 設備管理器(如硬件設備的唯一控制實例)。
- 緩存管理器(避免重復創建緩存對象)。
日志工具(確保日志寫入的唯一性)。
//ErrorLogger.h文件
#ifndef ERRORLOGGER_H
#define ERRORLOGGER_H#include <QString>
#include <QMutex>// 錯誤日志工具類(單例模式)
class ErrorLogger
{
public:// 獲取單例實例static ErrorLogger& getInstance();// 禁止拷貝和賦值ErrorLogger(const ErrorLogger&) = delete;ErrorLogger& operator=(const ErrorLogger&) = delete;// 寫入錯誤日志void writeLog(const QString& errorMessage);// 設置日志文件路徑(默認當前目錄下的error.log)void setLogFilePath(const QString& path);private:// 私有構造函數(單例模式)ErrorLogger();QString m_logFilePath; // 日志文件路徑QMutex m_mutex; // 互斥鎖,確保多線程安全
};#endif // ERRORLOGGER_H//ErrorLogger.cpp文件
#include "ErrorLogger.h"
#include <QFile>
#include <QTextStream>
#include <QDateTime>
#include <QDir>
#include <QDebug>ErrorLogger::ErrorLogger()
{// 默認日志路徑:當前程序目錄下的error.logm_logFilePath = QDir::currentPath() + "/error.log";
}ErrorLogger& ErrorLogger::getInstance()
{static ErrorLogger instance;return instance;
}void ErrorLogger::setLogFilePath(const QString& path)
{m_logFilePath = path;
}void ErrorLogger::writeLog(const QString& errorMessage)
{// 多線程加鎖,避免日志寫入沖突QMutexLocker locker(&m_mutex);// 獲取當前時間戳(格式:yyyy-MM-dd hh:mm:ss)QString timeStamp = QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss");// 構建日志內容(時間 + 錯誤信息)QString logContent = QString("[%1] Error: %2\n").arg(timeStamp).arg(errorMessage);// 打開文件(以追加模式,不存在則創建)QFile file(m_logFilePath);if (!file.open(QIODevice::WriteOnly | QIODevice::Append | QIODevice::Text)) {qDebug() << "無法寫入日志文件:" << file.errorString();return;}// 寫入日志QTextStream out(&file);out << logContent;file.close();
}//main.cpp文件
// 寫入錯誤日志(包含系統錯誤信息)ErrorLogger::getInstance().writeLog(QString("文件打開失敗: %1(路徑:%2)").arg(file.errorString()).arg(file.fileName()));
// 可選:設置自定義日志路徑(如程序數據目錄)ErrorLogger::getInstance().setLogFilePath(QDir::homePath() + "/myapp/logs/error.log");
spdlog第三方庫實現
鏈接: C++日志記錄庫SPDLog簡介
spdlog學習—安裝及基本使用
spdlog是一個高性能、超快速、零配置的C++日志庫,它旨在提供簡潔的API和豐富的功能,同時保持高性能的日志記錄。它支持多種輸出目標、格式化選項、線程安全以及異步日志記錄。
- 高性能:spdlog專為速度而設計,即使在高負載情況下也能保持良好的性能
- 零配置:無需復雜的配置,只需包含頭文件即可在項目中使用
- 異步日志:支持異步日志記錄,減少對主線程的影響
- 格式化:支持自定義日志消息的格式化,包括時間戳、線程ID、日志級別等
- 多平臺:跨平臺兼容,支持Windows、Linux、MacOS等操作系統
- 豐富的API:提供豐富的日志級別和操作符重載,方便記錄各種類型的日志
- 多目標輸出:可以將日志輸出到控制臺、普通文本文件、循環寫入文件(rotating log files)、每日生成新文件(daily logs)、系統日志等目標,同時也支持異步寫入以提高性能。
- 豐富的日志級別:Spdlog 支持常見的日志級別,如 TRACE、DEBUG、INFO、WARN、ERROR、CRITICAL 等,用戶可以根據需要選擇不同級別的日志輸出。
- 條件日志:根據預定義的條件開關,可以動態啟用或禁用特定級別的日志輸出。
使用步驟
spdlog庫的使用也非常簡單,只需要下載源代碼,然后把根目錄下的include目錄下的文件拷貝到我們的工程下,在工程中包含相應的頭文件即可。
- 控制臺打印
#include <spdlog/spdlog.h>#include <string.h>#include <iostream>int main(){// 普通打印spdlog::info("Welcome to info spdlog!");// 格式化打印// 打印字符串spdlog::info("Hello World {}", "spdlog!");// 打印數字spdlog::error("spdlog errCode : {}", -10020);// 指定打印數字的占位符spdlog::warn("spdlog format char {:08d}", 12);// 格式化打印不同進制的數據spdlog::critical("Support for int:{0:d} hex:{0:x} oct:{0:o} bin:{0:b}", 42);// 打印浮點型數據spdlog::info("float args are {:03.2f}", 1.23456);// 打印多個參數spdlog::info("string args are {0} {1}..", "too", "supported");spdlog::info("number args are {0} {1} {2}..", 10020, 10040, -100);system("pause");}
- 在文件中打印日志
#include <spdlog/spdlog.h>#include <spdlog/sinks/basic_file_sink.h>#include <string.h>#include <iostream>int main(){try{// 參數1 日志標識符, 參數2 日志文件名std::shared_ptr<spdlog::logger> mylogger = spdlog::basic_logger_mt("spdlog", "spdlog.log");// 設置日志格式. 參數含義: [日志標識符] [日期] [日志級別] [線程號] [數據]mylogger->set_pattern("[%n][%Y-%m-%d %H:%M:%S.%e] [%l] [%t] %v");mylogger->set_level(spdlog::level::debug);spdlog::flush_every(std::chrono::seconds(5)); // 定期刷新日志緩沖區mylogger->trace("Welcome to info spdlog!");mylogger->debug("Welcome to info spdlog!");mylogger->info("Welcome to info spdlog!");mylogger->warn("Welcome to info spdlog!");mylogger->error("Welcome to info spdlog!");mylogger->critical("Welcome to info spdlog!");// 刷新mylogger->flush_on(spdlog::level::debug);}catch (const spdlog::spdlog_ex& ex){std::cout << "Log initialization failed: " << ex.what() << std::endl;}system("pause");}
執行結果。執行程序后就會在當前目錄下生成一個spdlog.log文件,看下打印內容
特點
-
優點:
確保全局唯一實例,減少資源消耗(如頻繁創建銷毀實例的開銷),提供統一的訪問點。 -
缺點:
單例本質是全局變量,可能導致代碼耦合度升高;測試困難(單例狀態難以隔離);在多線程環境下需謹慎處理同步問題。
通過上述實現,單例模式能有效控制類的實例數量,在需要全局唯一訪問點的場景中非常實用。