单例模式是一种创建型设计模式,其核心是确保一个类在程序中只能存在唯一实例,并提供一个全局访问点。这种模式适用于需要集中管理资源(如日志、配置、连接池)的场景,避免资源冲突和重复创建的开销。

一、介绍

类型

单例模式的核心是限制实例化,但根据实例创建时机的不同,分为饿汉式懒汉式两种实现方式:

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 语言模…