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

C++ 单例模式学习

C++ 单例模式学习笔记

单例模式(Singleton Pattern)是 C++ 中最常用的设计模式之一,它保证一个类在程序运行期间只有一个实例,并提供全局访问点。无论是日志管理、配置中心还是设备驱动,单例模式都能有效避免资源竞争和重复初始化问题。本文将从基础原理到进阶实现,全面讲解 C++ 单例类的设计与优化。

一、单例模式的核心需求

在实际开发中,我们经常需要一个类全局唯一,例如:

  • 日志器:避免多实例导致日志文件混乱
  • 配置管理器:确保配置数据一致性
  • 数据库连接池:控制连接数量,避免资源浪费
  • 设备管理器:硬件资源只能被独占访问

单例模式需满足三个核心条件:

  1. 唯一实例:类只能创建一个对象
  2. 全局访问:提供便捷的全局访问方式
  3. 自主管理:实例的生命周期由类自身控制

二、单例模式的基础实现:饿汉式与懒汉式

C++ 单例模式有两种经典实现思路,分别对应不同的初始化时机。

1. 饿汉式单例(Eager Initialization)

原理:程序启动时(main 函数之前)就创建实例,确保线程安全,但可能提前占用资源。

// 饿汉式单例
class Singleton {
private:// 1. 私有构造函数:禁止外部创建实例Singleton() {// 初始化操作(如加载配置、打开文件)}// 2. 私有拷贝构造和赋值运算符:禁止复制Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;// 3. 私有静态实例:程序启动时初始化static Singleton instance;public:// 4. 公有静态方法:提供全局访问点static Singleton& GetInstance() {return instance;}// 其他成员函数void DoSomething() {// ...}
};// 类外初始化静态成员(关键步骤)
Singleton Singleton::instance;

优点

  • 实现简单,无需考虑线程安全问题
  • 访问速度快,实例已提前创建

缺点

  • 初始化时机早,可能浪费资源(如果程序全程未使用该单例)
  • 无法处理依赖关系(如初始化需要其他动态数据)

2. 懒汉式单例(Lazy Initialization)

原理:首次使用时才创建实例,避免资源浪费,但需要处理线程安全问题。

// 基础懒汉式单例(非线程安全)
class Singleton {
private:// 私有构造函数Singleton() {}// 禁止复制Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;// 私有静态指针:延迟初始化static Singleton* instance;public:// 公有访问方法:首次调用时创建实例static Singleton& GetInstance() {if (instance == nullptr) {  // 第一次检查instance = new Singleton();  // 线程不安全点}return *instance;}// 可选:手动释放资源(单例通常不需要,进程结束会自动释放)static void DestroyInstance() {if (instance != nullptr) {delete instance;instance = nullptr;}}
};// 初始化静态指针为nullptr
Singleton* Singleton::instance = nullptr;

优点

  • 延迟初始化,节省资源
  • 支持动态依赖关系

缺点

  • 基础版本在多线程环境下不安全(可能创建多个实例)
  • 需要手动管理内存(或依赖智能指针)

三、线程安全的懒汉式单例:从加锁到优化

基础懒汉式在多线程环境下存在风险:当多个线程同时通过 if (instance == nullptr) 检查时,会创建多个实例。解决线程安全问题是单例模式的核心难点。

1. 加锁的懒汉式(线程安全但效率低)

#include <mutex>// 加锁的懒汉式单例(线程安全)
class Singleton {
private:Singleton() {}Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;static Singleton* instance;static std::mutex mtx;  // 互斥锁public:static Singleton& GetInstance() {std::lock_guard<std::mutex> lock(mtx);  // 加锁(每次访问都加锁)if (instance == nullptr) {instance = new Singleton();}return *instance;}
};Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;

优点:线程安全
缺点:每次访问都需要加锁,性能开销大(尤其高频访问场景)

2. 双重检查锁定(Double-Checked Locking)

原理:通过两次检查避免不必要的加锁,兼顾线程安全和效率。

#include <mutex>// 双重检查锁定的懒汉式单例
class Singleton {
private:Singleton() {}Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;static Singleton* instance;static std::mutex mtx;public:static Singleton& GetInstance() {if (instance == nullptr) {  // 第一次检查:无锁,快速判断std::lock_guard<std::mutex> lock(mtx);  // 加锁if (instance == nullptr) {  // 第二次检查:确保只创建一次instance = new Singleton();}}return *instance;}
};Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;

为什么需要两次检查?

  • 第一次检查:避免已创建实例后每次访问都加锁,提高效率
  • 第二次检查:防止多个线程同时通过第一次检查后,重复创建实例

注意:在 C++11 之前,由于编译器优化和指令重排,双重检查锁定可能仍存在风险。C++11 及以后的标准已修复此问题,确保该模式安全。

3. 局部静态变量(C++11 后的最优解)

C++11 标准规定:局部静态变量的初始化在多线程环境下是线程安全的。这为单例模式提供了更简洁的实现方式。

// C++11 局部静态变量单例(推荐)
class Singleton {
private:// 私有构造函数Singleton() {}// 禁止复制Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;public:// 局部静态变量:首次调用时初始化,线程安全static Singleton& GetInstance() {static Singleton instance;  // C++11 保证线程安全初始化return instance;}
};

优点

  • 实现极简,一行核心代码
  • 天然线程安全(C++11 及以上)
  • 自动释放内存,无需手动管理
  • 延迟初始化,节省资源

缺点

  • 依赖 C++11 及以上标准(现代编译器均支持)
  • 无法控制析构顺序(如果多个单例存在依赖关系)

这是目前最推荐的单例实现方式,兼顾简洁性、线程安全性和资源效率。

四、单例模式的进阶问题与解决方案

1. 单例的析构顺序问题

当程序中存在多个单例时,它们的析构顺序与初始化顺序相反,但无法显式控制。如果单例 A 依赖单例 B,而 A 先析构,可能导致 B 在析构时访问已释放的资源。

解决方案:使用智能指针管理依赖关系,或通过手动释放函数控制顺序。

2. 单例的继承与扩展

单例类通常设计为不可继承(构造函数私有),但特殊场景下可能需要扩展。可通过模板基类实现通用单例:

// 单例模板基类
template <typename T>
class SingletonBase {
public:// 禁用拷贝SingletonBase(const SingletonBase&) = delete;SingletonBase& operator=(const SingletonBase&) = delete;// 全局访问点static T& GetInstance() {static T instance;  // 派生类实例return instance;}protected:// 保护构造函数:允许派生类构造SingletonBase() = default;virtual ~SingletonBase() = default;  // 虚析构函数
};// 派生单例类
class MySingleton : public SingletonBase<MySingleton> {// 友元声明:允许基类访问私有构造函数friend class SingletonBase<MySingleton>;private:// 私有构造函数MySingleton() {// 初始化逻辑}public:void DoSomething() {// 业务逻辑}
};

3. 单例的测试问题

单例模式会导致代码耦合度高,难以单元测试(全局状态难以隔离)。

解决方案

  • 测试环境中使用 mock 单例替代真实实现
  • 通过接口抽象单例功能,便于替换测试对象

五、单例模式的应用场景与禁忌

适合使用单例的场景

  • 全局资源管理(日志、配置、连接池)
  • 设备访问控制(打印机、传感器)
  • 工具类(全局唯一的工具实例)

不适合使用单例的场景

  • 需要多实例的类(如数据库连接,应使用连接池)
  • 频繁创建销毁的对象(单例生命周期过长)
  • 存在多线程写入竞争的场景(需额外加锁,影响性能)

六、总结:如何选择单例实现方式?

实现方式线程安全资源效率实现复杂度推荐场景
饿汉式简单初始化快、资源占用少的单例
局部静态变量是(C++11+)极简大多数场景(推荐)
双重检查锁定较复杂需兼容旧标准或手动管理内存

最终推荐:在 C++11 及以上环境中,优先使用局部静态变量实现单例,兼顾简洁性、线程安全性和资源效率。

单例模式看似简单,却涉及初始化时机、线程安全、资源管理等多方面问题。理解各种实现的优缺点,根据实际场景选择合适的方案,才能写出健壮的单例类。希望本文能帮助你掌握 C++ 单例模式的核心要点!

http://www.dtcms.com/a/343047.html

相关文章:

  • Spark读取MySQL数据库表
  • CSS【详解】性能优化
  • 什么是区块链?从比特币到Web3的演进
  • 深入浅出集成学习:从理论到实战,解锁机器学习 “集体智慧”
  • 新的 SHAMOS MacOS 窃取程序利用单行终端命令攻击用户
  • OceanBase 分区裁剪(Partition Pruning)原理解读
  • python + unicorn + xgboost + pytorch 搭建机器学习训练平台遇到的问题
  • Spring Boot集成腾讯云人脸识别实现智能小区门禁系统
  • windows无法访问某个服务器共享文件夹
  • linux中ctype.h字符判断函数实现原理
  • linux中的iptables的简介与常用基础用法
  • springboot人事管理系统源码和论文
  • Python编程练习100例(含答案)
  • HTTP 与 HTTPS:网络通信幽径上的安全秘钥
  • Promise详解:Promise解决ajax回调嵌套问题
  • system\core\init\init.cpp----LoadBootScripts()解析init.rc(2)
  • 五大主流ETL数据集成平台推荐
  • 鸿蒙 NEXT开发中轻松实现人脸识别功能
  • hadoop-3.3.6和hbase-2.4.13
  • 日志收集(ELK)
  • k8s--NetworkPolicy资源对象
  • 打工人项目日报计划
  • 搭建FTP文件共享服务器
  • linux 之 virtio 子系统核心的数据结构
  • DeepSeek R2难产:近期DeepSeek-V3.1 发布更新并开源,成功实现迈向 Agent 时代的第一步
  • 信息收集4----(收集网站指纹信息)
  • CSS 3D动画,围绕旋转动画Demo
  • 常见 Linux 网络命令梳理
  • AGV 技术落地场景解析:从制造业到仓储物流,看自动导引车的行业应用
  • 【Ruoyi解密-02.登录流程:】登录-找密码不抓瞎