当前位置: 首页 > news >正文

单例模式及优化

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

一、介绍

类型

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

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.lryc.cn/news/625053.html

相关文章:

  • 高防IP如何实现秒级切换?
  • 【Day 30】Linux-Mysql数据库
  • IDE开发系列(2)扩展的IDE框架设计
  • STC8单片机矩阵按键控制的功能实现
  • 分治-归并-493.翻转对-力扣(LeetCode)
  • Flutter 自定义 Switch 切换组件完全指南
  • Python 面向对象三大特性详解(与 C++ 对比)
  • Android Handler 线程执行机制
  • flutter项目适配鸿蒙
  • 【展厅多媒体】互动地砖屏怎么提升展厅互动感的?
  • 2025年最新美区Apple ID共享账号免费分享(持续更新)
  • 数组学习2
  • Java面试题储备14: 使用aop实现全局日志打印
  • 【HTML】document api
  • Vue 3中watch的返回值:解锁监听的隐藏技巧
  • C++---有符号和无符号整数的位移操作
  • RabbitMQ:数据隔离
  • kafka 冲突解决 kafka安装
  • Unity进阶--C#补充知识点--【Unity跨平台的原理】Mono与IL2CPP
  • 探索性测试:灵活找Bug的“人肉探测仪”
  • MongoDB Windows 系统实战手册:从配置到数据处理入门
  • keil错误:Error: failed to execute ‘D:\Keil\C51\BIN\BIN\A51.EXE‘
  • 【智慧工地源码】智慧工地云平台系统,涵盖安全、质量、环境、人员和设备五大管理模块,实现实时监控、智能预警和数据分析。
  • PYTHON让繁琐的工作自动化-猜数字游戏
  • 从数据汇总到高级分析,SQL 查询进阶实战(下篇)—— 分组、子查询与窗口函数全攻略
  • 车e估牵头正式启动乘用车金融价值评估师编制
  • CoRL 2025|隐空间扩散世界模型LaDi-WM大幅提升机器人操作策略的成功率和跨场景泛化能力
  • 从「行走」到「思考」:机器人进化之路与感知—决策链路的工程化实践
  • 第4.3节:awk正则表达式详解-特殊字符
  • Pytest测试框架基础及进阶