C++11起首选static局部变量实现单例,因其线程安全、懒加载、自动析构且由标准保证;需禁用拷贝/赋值,DCL仅限C++03或特殊需求。

直接用 static 局部变量,别手写双重检查锁(DCL)。这是 C++11 起标准保证线程安全、懒加载、自动析构的唯一推荐方式。
为什么 static 局部变量是首选
它不是“一种方案”,而是语言级保障:C++11 标准明文规定,函数内 static 局部变量的首次初始化是原子的、线程安全的。编译器(GCC/Clang/MSVC)都会生成等效于 std::call_once 的同步逻辑。
- 无需手动管理
std::mutex、std::atomic或内存序 - 构造函数只执行一次,其余并发调用会阻塞等待,不是忙等
- 析构在程序退出时由静态存储期自动触发,且在线程安全上下文中完成
- 支持构造函数抛异常:若初始化失败,下次调用仍会重试(符合预期)
std::call_once + std::unique_ptr 适合需要重置的场景
标准 static 局部变量无法在运行时销毁重建。如果你的单例要支持测试中反复初始化(比如重连数据库),就得换方案。
- 把实例托管给
std::unique_ptr<singleton></singleton>,配合static std::once_flag -
std::call_once确保 lambda 只执行一次,unique_ptr自动管理生命周期 - 需手动调用
reset()来释放旧实例;不调用则程序退出时自动析构 - 注意:lambda 不能捕获栈变量,避免析构竞争;
instance必须声明为static
示例关键片段:
立即学习“C++免费学习笔记(深入)”;
static std::once_flag flag;
static std::unique_ptr<Singleton> instance;
std::call_once(flag, []{ instance = std::make_unique<Singleton>(); });
手写双重检查锁(DCL)必须满足的硬性条件
除非兼容 C++03 或明确要求裸指针语义,否则不要碰。一旦漏掉任一条件,就是未定义行为。
-
instance必须是std::atomic<singleton></singleton>,不能是裸指针或volatile Singleton* - 首次读取用
load(std::memory_order_acquire),store 用store(ptr, std::memory_order_release) -
new Singleton()必须在std::lock_guard临界区内,且不能被编译器优化出锁外 - 加锁后必须二次检查
ptr == nullptr,且这次可用load(std::memory_order_relaxed)
错误示范:if (instance == nullptr) 直接判空——这会绕过原子操作,彻底失效。
饿汉式单例只在特定场景下可用
它把实例声明为类的 static 成员,在程序启动时就构造。天然线程安全,但牺牲了懒加载。
- 适合构造开销小、必然会被使用的单例(如日志器配置)
- 不适合含耗时操作(如网络连接、大文件加载)的场景,会拖慢启动
- 静态成员定义必须在 .cpp 文件中(如
Singleton Singleton::instance;),不能只在头文件里声明
真正容易被忽略的点:所有方案都必须显式删除拷贝和赋值——Singleton(const Singleton&) = delete; 和 Singleton& operator=(const Singleton&) = delete;。漏掉这条,任何线程安全设计都白搭。


















