C++ 单例模式两种实现方式
一、饿汉模式 (Eager Initialization)
特点:类加载时就立即创建实例
实现代码:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
优点:
- 线程安全:实例在程序启动时就创建,多线程访问时不会有竞争问题
- 简单直观:实现简单,不需要考虑线程同步
- 调用效率高:
getInstance()直接返回指针,没有锁开销
缺点:
- 资源占用早:即使不使用也会占用内存
- 启动慢:如果构造复杂,会影响程序启动速度
- 依赖顺序问题:多个饿汉单例的初始化顺序不确定
二、饱汉模式 (Lazy Initialization)
特点:第一次使用时才创建实例
基础版本(非线程安全):
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
线程安全版本(双检锁):
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
C++11 之后更简洁的线程安全版本:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
|
三、对比表格
| 特性 | 饿汉模式 | 传统饱汉模式(双检锁) | C++11静态局部变量 |
|---|---|---|---|
| 创建时机 | 程序启动时 (main函数之前) |
第一次调用 getInstance() 时 |
第一次调用 getInstance() 时 |
| 线程安全 | ✅ 天然线程安全 (静态初始化阶段) |
✅ 需要双检锁等机制 (手动实现) |
✅ 编译器保证线程安全 (C++11标准规定) |
| 资源占用 | ❌ 启动即占用内存 (即使不用) |
✅ 使用时才占用内存 | ✅ 使用时才占用内存 |
| 启动速度 | ⚠️ 可能较慢 (如果构造复杂) |
✅ 启动快 | ✅ 启动快 |
| 实现复杂度 | ✅ 简单 | ❌ 复杂 (需正确实现双检锁) |
✅ 非常简洁 (一行代码) |
| 性能开销 | ✅ 调用无锁开销 | ⚠️ 有锁检查开销 (即使已初始化) |
✅ 几乎无开销 (编译器优化) |
| 销毁控制 | ❌ 需手动管理 | ❌ 需手动管理 | ✅ 自动管理 (程序结束时) |
| C++版本要求 | C++98 | C++98(需要锁) C++11(原子操作) |
C++11及以上 |
| 内存泄漏风险 | ⚠️ 需正确释放 | ⚠️ 需正确释放 | ✅ 无泄漏风险 |
| 代码示例 | cpp<br>static T* instance = new T();<br> |
cpp<br>if (!instance) {<br> lock();<br> if (!instance) {<br> instance = new T();<br> }<br>}<br> |
cpp<br>static T instance;<br>return instance;<br> |
| 适用场景 | 1. 实例小、构造简单 2. 程序必用 3. 多线程环境 4. C++98项目 |
1. 实例大、构造复杂 2. 可能不用 3. C++98项目 4. 需要延迟初始化 |
绝大多数场景 1. C++11+项目 2. 需要延迟初始化 3. 要求代码简洁 4. 需要自动清理 |
性能对比数据
| 操作 | 饿汉模式 | 双检锁饱汉 | C++11静态局部变量 |
|---|---|---|---|
| 第一次调用 | 0-10 ns | 50-100 ns | 20-50 ns |
| 后续调用 | 0-5 ns | 10-20 ns | 0-5 ns |
| 内存占用 | 立即占用 | 延迟占用 | 延迟占用 |
| 线程竞争 | 无竞争 | 有锁竞争 | 首次有竞争,后续无 |
四、选择建议
使用饿汉模式当:
- 单例对象小,构造简单
- 程序运行期间一定会用到
- 对性能要求高,希望无锁访问
- 多线程环境下不想处理同步问题
使用饱汉模式当:
- 单例对象大,构造复杂或耗时
- 可能整个程序都不会用到该实例
- 对启动速度有要求
- 使用 C++11 或以上版本(可用静态局部变量)
到此这篇关于C++ 单例模式两种实现方式的文章就介绍到这了
更多推荐


所有评论(0)