
延迟加载单例必须解决线程安全问题
直接在 getInstance() 里 new 对象看似简单,但多线程下可能创建多个实例——这是最常踩的坑。C++11 起,static 局部变量的初始化天然线程安全,不用手写锁或 double-checked locking;但前提是编译器符合 C++11 或更高标准,且未禁用相关特性(如 GCC 的 -fno-threadsafe-statics)。
常见错误现象:getInstance() 被并发调用时,构造函数执行多次,或对象析构时崩溃(静态对象生命周期管理错乱)。
- 确保项目使用 C++11+ 标准(CMake 中设
set(CMAKE_CXX_STANDARD 11)) - 避免在构造函数里调用其他单例的
getInstance(),否则可能触发静态初始化顺序问题 - 若需在 DLL/so 中使用,注意 Windows 下 DLL 加载时机可能导致静态局部变量初始化失败
如何让单例支持显式销毁与重初始化
标准 static 局部变量方案无法手动释放资源,也无法在程序运行中“重置”单例。这时候得放弃自动存储期,改用手动管理指针 + 显式同步控制。
典型使用场景:单元测试中需要每次 clean state;插件系统中模块热重载;或依赖外部配置变更后重建实例。
立即学习“C++免费学习笔记(深入)”;
系统简介:冰兔BToo网店系统采用高端技术架构,具备超强负载能力,极速数据处理能力、高效灵活、安全稳定;模板设计制作简单、灵活、多元;系统功能十分全面,商品、会员、订单管理功能异常丰富。秒杀、团购、优惠、现金、卡券、打折等促销模式十分全面;更为人性化的商品订单管理,融合了多种控制和独特地管理机制;两大模块无限级别的会员管理系统结合积分机制、实现有效的推广获得更多的盈利!本次更新说明:1. 增加了新
- 用
std::atomic<:shared_ptr>></:shared_ptr>或std::atomic<t></t>存储指针,配合std::mutex保护首次创建和销毁过程 - 销毁接口(如
resetInstance())必须确保所有使用者已停止访问,否则出现 use-after-free - 不要在析构函数里调用
getInstance(),容易引发死锁或未定义行为
class LazySingleton {
public:
static LazySingleton& getInstance() {
LazySingleton* inst = instance.load();
if (!inst) {
std::lock_guard<:mutex> lock(mtx);
inst = instance.load();
if (!inst) {
inst = new LazySingleton();
instance.store(inst);
}
}
return *inst;
}
<pre class="brush:php;toolbar:false;">static void resetInstance() {
std::lock_guard<std::mutex> lock(mtx);
auto* old = instance.exchange(nullptr);
if (old) delete old;
}private:
static std::atomic
std::call_once + std::once_flag 是更可控的替代方案
当需要比 static 局部变量更强的控制力(比如延迟到某次特定调用才初始化、或绑定参数构造),std::call_once 比手写锁更轻量、语义更清晰,且同样线程安全。
性能影响极小:std::once_flag 内部通常用原子操作实现,无锁路径下几乎零开销;首次之后完全不进入同步区。
- 适用于构造函数带参数、或需从配置文件读取参数后再构造的场景
- 不能用于返回引用的单例(因为对象可能分配在堆上,需自行管理生命周期)
- 注意
std::call_once抛异常会导致后续调用永远卡住(std::system_error),务必保证初始化函数不抛异常,或提前捕获
别忽略析构顺序和静态对象生命周期
延迟加载单例如果依赖其他全局对象(比如日志器、配置管理器),而后者又在 main 返回后才析构,就可能在单例析构时访问已被销毁的对象——这类 crash 很难复现,但线上高频发生。
最容易被忽略的一点:C++ 不保证不同编译单元中静态对象的析构顺序,只保证同一单元内按构造逆序析构。
- 把单例析构逻辑移到
atexit()回调里?不行——atexit注册顺序也不受控,且无法传参 - 更稳妥的做法是:不依赖其他单例的析构期行为;或统一用 “init/destroy” 成对 API,由上层明确控制生命周期边界
- 若必须依赖,考虑用弱引用(
std::weak_ptr)持有,析构前先检查是否还有效









