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

设计模式 | 单例模式——饿汉模式 懒汉模式

单例模式

文章目录

  • 单例模式
    • 一、饿汉模式(Eager Initialization)
      • 1. 定义
      • 2. 特点
      • 3. 饿汉单例模式(定义时-类外初始化)
      • 4. 实现细节
    • 二、懒汉模式(Lazy Initialization)
      • 1. 定义
      • 2. 特点
      • 3. 懒汉单例模式(第一次调用时-初始化)
      • 4. 多线程不安全(需加锁)
    • 三、对比 & 使用建议

一、饿汉模式(Eager Initialization)

1. 定义

类加载时就创建实例,不管你用不用,先创建再说。

2. 特点

  • 线程安全(因为类加载是线程安全的)
  • 启动时就分配资源,资源消耗可能较大

3. 饿汉单例模式(定义时-类外初始化)

#include <iostream>class TaskQueue {
public:// 静态方法:获取唯一实例指针static TaskQueue* getInstance() {return m_taskQ;  // 返回静态成员变量指针}// 删除拷贝构造函数:防止复制实例(例如 TaskQueue b = a)TaskQueue(const TaskQueue&) = delete;// 删除赋值运算符:防止赋值复制(例如 a = b)TaskQueue& operator=(const TaskQueue&) = delete;private:// 默认构造函数私有化:禁止类外部构造对象// 外部无法通过 new TaskQueue() 或 TaskQueue t; 构造对象TaskQueue() = default;// 静态成员变量声明:用于保存唯一实例的指针static TaskQueue* m_taskQ;
};// ⚠️ 类外定义并初始化静态成员变量:这一行非常关键!
// ✅ 这是 TaskQueue 类的“静态成员变量定义+初始化”
// ✅ new TaskQueue 调用了 private 构造函数,但因为这是类自己的代码(初始化自己的静态成员),所以**允许访问私有成员**
TaskQueue* TaskQueue::m_taskQ = new TaskQueue;
// --------------------------------------------
// ⬆️ 虽然这行“写在类外”(语法上),但它是类的一部分(静态成员初始化),它仍然被认为是类自己的代码(类内部行为),所以可以访问私有构造函数。
// C++ 标准允许它访问类的 private 构造函数。
// 所以不会报错,而是合法的。int main() {// 获取单例对象的指针TaskQueue* q1 = TaskQueue::getInstance();TaskQueue* q2 = TaskQueue::getInstance();// 打印地址验证是否为同一实例std::cout << "q1 地址: " << q1 << std::endl;std::cout << "q2 地址: " << q2 << std::endl;// 输出地址肯定一样return 0;
}

注意:

// 静态成员变量定义和初始化(在类外完成)
TaskQueue* TaskQueue::m_taskQ = new TaskQueue;

这句代码在程序启动时就执行,立即创建了 TaskQueue 的唯一实例:

  1. 是静态变量,生命周期贯穿整个程序;
  2. 实例在任何 getInstance() 调用之前就已创建完成;
  3. getInstance() 只是简单地返回这个已创建好的指针。

因此,它就是一个标准的饿汉单例模式实现。

4. 实现细节

  1. 为什么TaskQueue* TaskQueue::m_taskQ = new TaskQueue;属于类内访问,可以访问private构造函数?
    是因为它是“静态变量”?“私有变量”?还是“初始化”这件事本身?
条件是否是关键解释
这是类的成员定义✅ 是关键初始化 TaskQueue::m_taskQ 是类的一部分,因此有权限访问类的私有成员
static 成员❌ 不是核心原因虽然需要类外初始化,但并不是 static 带来了访问权限
private 变量❌ 更不是原因private 表示“只能被类的代码访问”,而这行被视为类的代码

不管是 static 还是 private,关键原因在于:这是类的“成员变量定义”,属于类的内部实现,因此它拥有类的访问权限。

  1. 延申:若把 new TaskQueue 写在 main()

❌ 非法代码(main 函数中访问私有构造函数)

int main() {TaskQueue* q = new TaskQueue();  // ❌ 错!构造函数是 private
}

为什么报错?

  • main() 是类外部的普通代码。
  • 它不是类成员,不被视为类内部实现。
  • 因此没有权限访问私有构造函数,编译器会直接报错。

✅ 合法代码(类外定义静态成员时调用私有构造函数)

TaskQueue* TaskQueue::m_taskQ = new TaskQueue;  // ✅ 对!

为什么合法?

  • 这是类在定义和初始化自己的静态成员变量。
  • 虽然代码写在类外,但它被视为类的一部分(属于 TaskQueue 类实现)。
  • 所以有权访问 private 构造函数。
  • C++ 语法明确允许这种访问。

二、懒汉模式(Lazy Initialization)

1. 定义

在第一次访问时才创建实例,延迟到真正需要的时候再进行初始化。

2. 特点

  • 延迟加载:只有在首次调用 getInstance() 时才会创建实例,节省系统资源;
  • 线程不安全(默认实现),但可以通过加锁、双重检查、std::call_once 等方式实现线程安全
  • 相较于饿汉模式,更灵活、更节省资源,但实现稍复杂。

3. 懒汉单例模式(第一次调用时-初始化)

#include <iostream>class TaskQueue {
public:// ❌ 没有加锁,线程不安全 ******不同点******static TaskQueue* getInstance() {if (m_taskQ == nullptr) {    m_taskQ = new TaskQueue(); // ❌不安全,可能多个线程同时执行这里,创建多个实例}return m_taskQ;}TaskQueue(const TaskQueue&) = delete;TaskQueue& operator=(const TaskQueue&) = delete;private:TaskQueue() = default;static TaskQueue* m_taskQ;
};// 初始化静态实例指针 ******不同点******
TaskQueue* TaskQueue::m_taskQ = nullptr;int main() {TaskQueue* q1 = TaskQueue::getInstance();TaskQueue* q2 = TaskQueue::getInstance();std::cout << "q1 地址: " << q1 << std::endl;std::cout << "q2 地址: " << q2 << std::endl;// 输出地址一样(如果线程不冲突)return 0;
}

4. 多线程不安全(需加锁)

线程冲突时,多个线程可能在getInstance()创建多个对象,需要加锁!!!

三、对比 & 使用建议

对比项饿汉模式(Eager Singleton)懒汉模式(Lazy Singleton)
实例创建时机程序启动时 / 类加载时立即创建第一次调用 getInstance() 时才创建
资源占用无论是否使用都会占用资源仅在需要时才占用资源,更节省内存
线程安全✅ 天然线程安全(由 C++ 静态初始化保证)❌ 默认线程不安全,需手动加锁处理
实现难度实现简单,逻辑清晰实现复杂(涉及锁、双检、或 call_once)
性能开销启动时略高,占用资源可能浪费每次调用 getInstance() 可能涉及锁(效率略低)
适用场景实例始终会用到,资源占用可接受实例可能不一定会用到,或实例化代价较高
常用实现类外初始化静态成员指针(如:new Singleton;内部判断是否为 null + 加锁后 new Singleton();
示例构造代码TaskQueue* m = new TaskQueue;(类外直接构造)if (!m) m = new TaskQueue;(函数内延迟构造)
可扩展性不容易扩展为参数化构造初始化时可自定义参数(但需额外设计)
使用场景推荐模式
实例一定会被频繁使用✅ 饿汉模式(简单稳定)
实例创建代价高或可能不用✅ 懒汉模式(延迟创建)
多线程访问高频✅ 饿汉 或 call_once 懒汉
希望按需控制生命周期✅ 懒汉更灵活

相关文章:

  • 从零开始的云计算生活——第二十天,脚踏实地,SSH与Rsync服务
  • uni-app总结5-UTS插件开发
  • Axios 拦截器实现原理深度剖析:构建优雅的请求处理管道
  • Vue-11-前端框架Vue之应用基础父组件传值到子组件props的使用
  • TDengine 集群超能力:超越 InfluxDB 的水平扩展与开源优势
  • 具身机器人
  • Oracle/MySQL/SqlServer/PostgreSQL等数据库的数据类型映射以及各版本数据类型情况说明
  • SQL等价改写优化
  • VACM 详解:SNMPv3 的访问控制核心
  • 国产ARM/RISCV与OpenHarmony物联网项目(六)SF1节点开发
  • java+springboot注释介绍+使用介绍
  • Docker制作镜像
  • Sentinel(一):Sentinel 介绍和安装
  • 设计模式之五大设计原则(SOLID原则)浅谈
  • 基于 OpenCV 的图像亮度、对比度与锐度调节
  • 攻防演练:1.木马后门文件演练
  • Neo4j操作指南:修改节点数据与新增节点属性
  • Android Framework阅读经验
  • SCRM软件数据分析功能使用指南:从数据挖掘到商业决策
  • PL端软核FIFO读写
  • 做网站常用什么软件/关键词大全
  • 做分子生物实验常用网站/网页制作成品模板网站
  • 合肥 网站建设/承接网络推广外包业务
  • 网站开发可行性报告/seo排名优化公司
  • 常州手机网站建设/短视频平台推广方案
  • 做a动态网站/淘宝推广软件