C++11起应统一使用static局部变量实现单例,它天然线程安全、支持懒加载、自动析构;手动new、DCLP或全局指针均不推荐。

饿汉式单例:启动即构造,天然线程安全但可能浪费资源
饿汉式在程序加载时就完成实例初始化,static局部变量或全局静态对象保证了“一次且仅一次”构造,C++11 起所有编译器对 static 局部变量的初始化都做了线程安全处理(调用 std::call_once 或等效机制)。
常见错误是手动用 new + 全局指针模拟饿汉式,比如:
Singleton* instance = nullptr; // ❌ 手动管理,不安全
void init() { instance = new Singleton(); }
正确写法应依赖语言特性:
- 用
static局部变量(推荐):static Singleton& getInstance() { static Singleton instance; return instance; } - 或用
static成员变量 + 静态初始化(需确保定义在 .cpp 中):static Singleton Singleton::instance; - 不支持延迟初始化——哪怕从不调用
getInstance(),构造函数也会执行
懒汉式单例:首次调用才创建,必须显式加锁防竞态
懒汉式默认不是线程安全的。典型错误是只检查指针是否为空再 new,多线程下可能同时通过判空、各自构造两次。
立即学习“C++免费学习笔记(深入)”;
最简可行方案(C++11+):
static Singleton& getInstance() {
static std::once_flag flag;
static Singleton* instance = nullptr;
std::call_once(flag, []{ instance = new Singleton(); });
return *instance;
}
但更推荐直接用 static 局部变量实现懒汉语义——它既懒又安全:
-
static Singleton& getInstance() { static Singleton instance; return instance; }—— 这行代码就是懒汉式+线程安全的全部 - C++11 标准规定:首次控制流经过该声明时才初始化,且初始化过程由实现保证原子性
- 无需手动
delete,析构在程序退出时自动执行(满足单例生命周期要求)
为什么不用 double-checked locking(DCLP)?
手写双重检查锁定(如先读指针、加锁、再读指针、再 new)在 C++ 中极易出错,原因包括:
- 编译器重排:
new的三步(分配内存 → 构造 → 赋值给指针)可能被重排,导致其他线程看到未构造完的对象 - CPU 乱序:即使加
volatile也无法阻止硬件级重排(C++11 前无标准内存模型) - 修复需用
std::atomic+memory_order,代码复杂度远超收益
结论:static 局部变量是 C++11 及以后唯一应考虑的单例写法,它自动覆盖饿汉/懒汉语义、线程安全、自动析构三大需求。
注意析构顺序与静态对象生命周期
用 static 局部变量实现的单例,其析构发生在 main() 返回后、全局对象析构期间,顺序与构造顺序相反。这意味着:
- 如果其他静态对象的析构函数中调用了该单例,行为未定义(可能已析构)
- 无法控制单例比某些静态对象“活得更久”,这是 C++ 静态生命周期固有限制
- 若真需要可控销毁,只能退回到裸指针 + 手动
new/delete,但必须自行管理线程安全和调用时机
实际项目中,绝大多数单例根本不需要显式销毁——只要不依赖其他静态对象的析构逻辑,static 局部变量方案足够健壮。










