C++11起最安全高效的单例实现是静态局部变量,因其初始化由编译器自动生成线程安全逻辑(等效std::call_once),无需手动管理内存序、原子操作或锁,避免了DCL易出错的重排与半构造对象风险。

直接用 static 局部变量,别手写 Double-Checked Locking(DCL)。它容易出错、性能不比编译器生成的代码好,还增加维护成本。
为什么 DCL 在 C++ 里极易写错
核心问题不是“加没加锁”,而是内存可见性与指令重排。C++11 前完全不可靠;C++11 后若漏掉任意一项,仍会崩溃或读到半构造对象:
-
instance必须是std::atomic<Singleton*>,不能是裸指针 - 首次读必须用
load(std::memory_order_acquire),否则后续读可能被重排到前面 -
new Singleton()必须在std::lock_guard保护内,且不能被编译器优化出临界区 -
store(ptr, std::memory_order_release)必须在锁内完成,否则其他线程看到非空指针时,对象状态不可见 - 用
volatile修饰指针毫无作用——它不提供同步语义,也阻止不了重排
如果你真要手写 DCL:必须满足的最小条件
仅当需精确控制初始化时机(比如构造失败后重试)、或兼容 C++03 环境时才考虑。以下是最简可行骨架:
class Singleton {
public:
static Singleton* getInstance() {
Singleton* ptr = instance.load(std::memory_order_acquire);
if (ptr == nullptr) {
std::lock_guard<std::mutex> lock(mutex_);
ptr = instance.load(std::memory_order_relaxed); // 加锁后再查一次
if (ptr == nullptr) {
ptr = new Singleton(); // 必须在此处,且不能被移出锁外
instance.store(ptr, std::memory_order_release);
}
}
return ptr;
}
<p>private:
Singleton() = default;
static std::atomic<Singleton*> instance;
static std::mutex mutex_;
};</p><p>std::atomic<Singleton*> Singleton::instance{nullptr};
std::mutex Singleton::mutex_;注意:std::memory_order_relaxed 用于锁内二次检查,避免无谓的 acquire 开销;std::memory_order_acquire/release 是保证构造结果对其他线程可见的底线。
立即学习“C++免费学习笔记(深入)”;
静态局部变量方案为何更优
它不是“替代方案”,而是标准规定的正确解法。C++11 起,static Singleton instance; 的初始化由编译器自动生成等效于 std::call_once 的保护逻辑:
- 无需手动管理内存(无
new/delete,无泄漏风险) - 自动延迟加载,且首次调用后零开销
- 析构由静态存储期管理,在
main返回后安全执行 - 不依赖程序员对 memory order 的理解,天然规避所有重排陷阱
唯一需留意的是:若单例构造函数中调用了其他静态单例的 getInstance(),可能触发静态初始化顺序问题——这不是方案缺陷,而是所有跨静态对象依赖共有的限制。
真正难的从来不是“怎么写 DCL”,而是判断是否真的需要它。绝大多数项目,直接用静态局部变量就够了;剩下那些真需要精细控制的,往往已超出单例本身的设计范畴。


















