單例模式及優化

單例模式是一種創建型設計模式,其核心是確保一個類在程序中只能存在唯一實例,并提供一個全局訪問點。這種模式適用于需要集中管理資源(如日志、配置、連接池)的場景,避免資源沖突和重復創建的開銷。

一、介紹

類型

單例模式的核心是限制實例化,但根據實例創建時機的不同,分為餓漢式懶漢式兩種實現方式:

1. 餓漢式(Eager Initialization)
  • 特點程序啟動時(類加載階段)就創建實例,無論是否使用。
  • 優點:實現簡單,天然線程安全(C++中全局變量初始化在主線程執行)。
  • 缺點:如果實例占用資源大且始終未被使用,會造成資源浪費。
2. 懶漢式(Lazy Initialization)
  • 特點第一次使用時才創建實例,延遲初始化。
  • 優點:避免資源浪費,適合實例化成本高的場景。
  • 缺點:基礎實現線程不安全,需要額外處理多線程同步問題。
    選擇餓漢式還是懶漢式,需根據實例化成本、使用頻率和線程環境綜合判斷:資源占用小且必用→餓漢式;資源占用大或不一定使用→懶漢式。
單例模式的優點
  1. 唯一實例
    • 確保類在整個程序中只有一個實例,避免資源沖突
  2. 全局訪問
    • 提供統一的訪問點,方便在程序任何地方使用
  3. 資源控制
    • 集中管理資源(如文件、網絡連接),避免重復創建銷毀
  4. 延遲初始化
    • 只有在首次使用時才初始化,節省系統資源
  5. 線程安全(優化后)
    • 現代實現可保證多線程環境下的安全性
單例模式的適用場景
  1. 全局資源管理器
    • 日志管理器:確保所有日志寫入同一文件/系統
    • 配置管理器:全局共享一份配置數據
    • 連接池:數據庫/網絡連接的統一管理
  2. 設備訪問控制
    • 打印機管理器:避免多個程序同時操作硬件
    • 傳感器接口:確保數據讀取的一致性
  3. 全局狀態存儲
    • 應用程序上下文:存儲全局狀態信息
    • 緩存管理器:全局共享緩存數據

二、實現

以一個線程安全的日志管理器為例

#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 已關閉
應用場景
  1. 日志系統
    • 整個應用使用同一個日志實例,確保日志順序和完整性
  2. 配置管理
    • 讀取配置文件后,全局共享配置信息,避免重復IO操作
  3. 數據庫連接池
    • 管理數據庫連接的創建和復用,防止連接數爆炸
  4. GUI應用
    • 主窗口實例:確保應用程序只有一個主窗口
    • 對話框管理器:統一管理對話框的創建和銷毀
  5. 硬件交互
    • 如打印機、攝像頭等設備的訪問控制,避免沖突

三、優化

優化點
  1. 通用單例基類
    • 使用CRTP(奇異遞歸模板模式)實現通用單例基類,避免重復代碼
    • 派生類只需繼承Singleton<Derived>即可獲得單例特性
  2. 增強的線程安全
    • 采用雙重檢查鎖定(DCLP)+ 原子變量,在保證線程安全的同時減少鎖競爭
    • C++11及以上標準確保局部靜態變量初始化的線程安全性
  3. 資源管理優化
    • 使用std::unique_ptr管理實例,確保自動釋放資源,避免內存泄漏
    • 提供destroyInstance()方法,支持在測試場景下手動銷毀實例
  4. 功能擴展
    • 增加日志級別過濾,可動態設置日志輸出粒度
    • 支持帶時間戳(精確到毫秒)和級別標記的日志格式
    • 控制臺輸出支持彩色顯示,區分不同日志級別
  5. 可測試性提升
    • 允許手動銷毀實例,支持單元測試中的狀態重置
    • 清晰的接口設計便于模擬(Mock)測試
  6. 現代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] 程序退出
優化后的優勢
  1. 更高的復用性
    • 通用單例基類可被多個類復用,減少代碼冗余
  2. 更好的性能
    • 雙重檢查鎖定減少了鎖競爭,提高多線程環境下的性能
  3. 更靈活的功能
    • 日志級別過濾等擴展功能使單例類更實用
  4. 更安全的資源管理
    • 智能指針和RAII確保資源正確釋放,避免內存泄漏
  5. 更好的可測試性
    • 支持手動銷毀實例,便于單元測試和狀態重置

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/bicheng/93800.shtml
繁體地址,請注明出處:http://hk.pswp.cn/bicheng/93800.shtml
英文地址,請注明出處:http://en.pswp.cn/bicheng/93800.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Dockerfile優化指南:利用多階段構建將Docker鏡像體積減小90%

更多如果你已經跟隨我們之前的教程&#xff0c;親手將自己的應用裝進了Docker這個“魔法盒子”&#xff0c;那你可能很快就會遇到一個幸福但又尷尬的煩惱&#xff1a;你親手構建的Docker鏡像&#xff0c;竟然像一個塞滿了石頭和棉被的行李箱&#xff0c;臃腫不堪&#xff0c;笨…

英文PDF翻譯成中文怎么做?試試PDF翻譯工具

在全球化快速發展的時代&#xff0c;跨語言交流變得愈發頻繁&#xff0c;無論是學術研究、商務合作還是日常學習&#xff0c;都離不開一個高效、準確的翻譯工具。尤其是對于PDF文件的翻譯需求&#xff0c;更是日益增長。今天&#xff0c;就讓我們一起深入了解幾款在PDF翻譯領域…

macOS使用brew切換Python版本【超詳細圖解】

目錄 一、更新Homebrew倉庫 二、安裝pyenv 三、將pyenv添加到bash_profile文件中 四、使.bash_profile文件的更改生效 五、安裝需要的Python版本 六、設置全局使用的Python版本 七、檢查Python版本是否切換成功 pyenv常用命令 一、更新Homebrew倉庫 brew update 這個…

[矩陣置零]

初始思路分析 這段代碼實現了將矩陣中元素為0的行和列全部置零的功能。主要思路是使用標記數組記錄需要置零的行和列。以下是詳細分析&#xff1a; 1. 初始化階段 int m matrix.size(); int n matrix[0].size(); vector<bool> row(m), col(n);獲取矩陣的行數m和列數n創…

redis-集成prometheus監控(k8s)

一. 簡介&#xff1a; 關于redis的簡介和部署&#xff0c;可以參考單獨的文章redis-sentinel基礎概念及部署-CSDN博客&#xff0c;這里就不細說了。這里只講講如何在k8s中部署export并基于prometheus做redis的指標采集。 二. 實現方式&#xff1a; 首先我們需要先部署exporter…

OVS:ovn為什么默認選擇Geneve作為二層隧道網絡協議?

首先確認 Geneve 是一種封裝協議,可能提供比 VLAN 或 VXLAN 更靈活的擴展能力,這對 OVN 的多租戶場景很重要。可能需要支持更多元數據字段,比如攜帶網絡策略信息,這符合 SDN 集中控制的需求。 性能方面需要考慮封裝效率和硬件支持情況,雖然 Geneve 頭部稍大,但現代網卡的…

grep命令要點、詳解和示例

grep技術要點 1) 工作模型&#xff08;3 件事&#xff09; 輸入&#xff1a;從文件或標準輸入&#xff08;-&#xff09;讀入&#xff0c;一次按“行”處理&#xff08;除非用 -z 改成以 NUL 作為“行”分隔&#xff09;。匹配&#xff1a;把每一行拿去和模式&#xff08;patte…

nVidia Tesla P40使用anaconda本地重編譯pytorch3d成功加載ComfyUI-3D-Pack

背景 自己用的是nVidia Tesla P40&#xff0c;垃圾佬專屬卡 使用下面的由YanWenKun提供的ComfyUI-3D-Pack預安裝環境&#xff0c;但在本地編譯pytorch3d這一步出錯&#xff0c;后面有出錯信息&#xff0c;如果有和我一樣的卡一樣的問題&#xff0c;參看此文的解決方法 老版本…

網絡基礎——協議認識

文章目錄網絡基礎網絡的發展——引出一些概念協議認識初識協議協議分層協議分層的模型再談協議為什么要有TCP/IP協議TCP/IP協議的宏觀認識宏觀理解TCP/IP協議和操作系統的關系協議的真正本質網絡基礎 本篇文章&#xff0c;我們將正式進入網絡部分的學習。這是網絡部分的第一篇…

云原生俱樂部-RH134知識點總結(2)

這一章的內容也會比較多&#xff0c;因為預期三篇文章更完RH134系列&#xff0c;所以每章安排的內容都比較多&#xff0c;并且RH134上面的都是重點&#xff0c;一點也不好寫。昨天一天將RH124系列寫完了&#xff0c;今天爭取將RH134系列寫完。至于我為什么要著急將這些寫完&…

深度學習-計算機視覺-微調 Fine-tune

1. 遷移學習遷移學習&#xff08;transfer learning&#xff09;是一種機器學習方法&#xff0c;通過將源數據集&#xff08;如ImageNet&#xff09;上訓練得到的模型知識遷移到目標數據集&#xff08;如特定場景的椅子識別任務&#xff09;。這種方法的核心在于利用預訓練模型…

STL庫——string(類函數學習)

? ? ? ? ? づ?ど &#x1f389; 歡迎點贊支持&#x1f389; 個人主頁&#xff1a;勵志不掉頭發的內向程序員&#xff1b; 專欄主頁&#xff1a;C語言&#xff1b; 文章目錄 前言 一、STL簡介 二、string類的優點 三、標準庫中的string類 四、string的成員函數 4.1、構造…

登上Nature!清華大學光學神經網絡研究突破

2025深度學習發論文&模型漲點之——光學神經網絡光學神經網絡的基本原理是利用光的傳播、干涉、衍射等特性來實現神經網絡中的信息處理和計算。在傳統神經網絡中&#xff0c;信息以電信號的形式在電子元件之間傳輸和處理&#xff0c;而在光學神經網絡中&#xff0c;信息則以…

【java】對word文件設置只讀權限

文件流輸出時 template.getXWPFDocument().enforceCommentsProtection(); 文件輸出時 //打開己創建的word文檔 XWPFDocument document new XWPFDocument(new FileInputStream("output.docx")); //設置文檔為只讀 document.enforceReadonlyProtection(); //保存文…

Zookeeper 在 Kafka 中扮演了什么角色?

在 Apache Kafka 的早期架構中&#xff0c;ZooKeeper 扮演了分布式協調服務角色&#xff0c;負責管理和協調整個 Kafka 集群。 盡管新版本的 Kafka 正在逐步移除對 ZooKeeper 的依賴&#xff0c;但在許多現有和較早的系統中&#xff0c;了解 ZooKeeper 的作用仍然非常重要。 Zo…

什么叫做 “可迭代的產品矩陣”?如何落地??

“可迭代的產品矩陣” 不是靜態的產品組合&#xff0c;而是圍繞用戶需求與商業目標構建的動態生態。它以核心產品為根基&#xff0c;通過多維度延伸形成產品網絡&#xff0c;同時具備根據市場反饋持續優化的彈性&#xff0c;讓產品體系既能覆蓋用戶全生命周期需求&#xff0c;又…

Nginx代理配置詳解:正向代理與反向代理完全指南

系列文章索引&#xff1a; 第一篇&#xff1a;《Nginx入門與安裝詳解&#xff1a;從零開始搭建高性能Web服務器》第二篇&#xff1a;《Nginx基礎配置詳解&#xff1a;nginx.conf核心配置與虛擬主機實戰》第三篇&#xff1a;《Nginx代理配置詳解&#xff1a;正向代理與反向代理…

Vue3 Element-plus 封裝Select下拉復選框選擇器

廢話不多說&#xff0c;樣式如下&#xff0c;代碼如下&#xff0c;需要自取<template><el-selectv-model"selectValue"class"checkbox-select"multiple:placeholder"placeholder":style"{ width: width }"change"change…

jenkins 自動部署

一、win10 環境安裝&#xff1a; 1、jdk 下載安裝&#xff1a;Index of openjdk-local 2、配置環境變量&#xff1a; 3、jenkins 下載&#xff1a;Download and deploy 下載后的結果&#xff1a;jenkins.war 4、jenkins 啟動&#xff1a; 5、創建管理員用戶 admin 登錄系統…

2020 GPT3 原文 Language Models are Few-Shot Learners 精選注解

本文為個人閱讀GPT3&#xff0c;部分內容注解&#xff0c;由于GPT3原文篇幅較長&#xff0c;且GPT3無有效開源信息 這里就不再一一粘貼&#xff0c;僅對原文部分內容做注解&#xff0c;僅供參考 詳情參考原文鏈接 原文鏈接&#xff1a;https://arxiv.org/pdf/2005.14165 語言模…