C++11起推荐用静态局部变量实现线程安全单例,因编译器保证其首次初始化的原子性;或用std::call_once配合once_flag,天然禁止重排、无锁且仅首次调用有开销。

为什么双重检查锁定(DCLP)在C++11之前不安全
因为编译器重排序和CPU指令重排,new Singleton()可能被拆成“分配内存→写入对象→赋值给静态指针”三步,而第二步和第三步可能被调换。线程A执行到一半时,线程B看到指针非空但对象未构造完成,直接使用就会崩溃。
解决方法是用 std::atomic + memory_order_acquire/release 或依赖C++11起的 std::call_once 和 std::once_flag——它们天然禁止重排且可移植。
用 std::call_once 实现最简线程安全单例
这是目前推荐做法:无需手动管理锁、无重排风险、代码清晰、性能开销仅在首次调用时发生。
-
static std::once_flag flag;必须是静态局部变量或全局,不能是成员变量(否则每个实例都有一份) -
std::call_once(flag, []{ instance = new Singleton(); });中的 lambda 必须捕获不到外部栈变量(避免析构竞争),建议用纯函数式初始化 - 注意
instance本身也要是static,否则多线程下可能多次构造
class Singleton {
public:
static Singleton& getInstance() {
static std::once_flag flag;
static Singleton* instance = nullptr;
std::call_once(flag, []{
instance = new Singleton();
});
return *instance;
}
private:
Singleton() = default;
};为什么不用 std::mutex 手动加锁
虽然可行,但每次调用 getInstance() 都要进临界区,哪怕只初始化一次,性能损耗明显;而且容易漏写 lock()/unlock() 或异常导致死锁。
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
立即学习“C++免费学习笔记(深入)”;
- 如果用
std::lock_guard包裹整个函数体,首次之后仍是无谓加锁 - 若只锁初始化分支,又得配合指针判空+双重检查,回到DCLP老问题,还得手动加
atomic和内存序 - C++11后标准库已提供更轻量、更可靠的原语,没必要重复造轮子
静态局部变量方案(C++11起隐式线程安全)
最简洁写法,靠编译器保证首次访问时的初始化原子性:
class Singleton {
public:
static Singleton& getInstance() {
static Singleton instance; // C++11起,这里自动线程安全
return instance;
}
private:
Singleton() = default;
};注意点:
- 必须是
static局部变量,不能是static成员变量(后者不享受该保障) - 析构时机由编译器决定,在 main() 返回后、静态对象销毁阶段执行,无法控制顺序
- 如果构造函数抛异常,下次调用仍会重试——这点和
std::call_once行为一致
真正难处理的是单例间依赖和销毁顺序,而不是创建时的线程安全。只要用对语法糖或标准原语,初始化本身早就不该是痛点了。

















