单例不能用全局变量代替,因其不满足延迟初始化、全局唯一和线程安全三要素;C++11起推荐用local static实现,简洁且线程安全。

为什么不能用全局变量代替单例
全局变量在多线程环境下不安全,且无法控制初始化时机——static局部变量在 C++11 起才保证线程安全的首次初始化,而全局对象的构造顺序跨编译单元不可控,容易引发 undefined behavior。单例的核心诉求是「延迟初始化 + 全局唯一 + 线程安全」,这三点全局变量一个都做不到。
最简安全写法:C++11 以后推荐用 local static
利用函数内 static 变量的“首次调用时初始化”和“C++11 标准保证的线程安全”特性,代码极简且无锁:
class Singleton {
public:
static Singleton& getInstance() {
static Singleton instance; // ✅ 线程安全,仅首次调用构造
return instance;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default; // 可加日志或资源初始化
};
注意点:
-
getInstance()必须返回引用(Singleton&),返回值会触发拷贝,破坏单例语义 - 构造函数设为
private,防止外部new或直接实例化 - 显式删除拷贝构造与赋值操作符,避免误用
- 不要在构造函数里调用其他单例的
getInstance(),可能触发静态初始化顺序问题
需要手动管理生命周期?慎用 std::unique_ptr + new
只有在必须延迟析构(比如依赖其他单例销毁后才释放)或需定制销毁逻辑时,才考虑堆分配。但代价是引入显式内存管理风险:
立即学习“C++免费学习笔记(深入)”;
class Singleton {
public:
static Singleton& getInstance() {
static std::unique_ptr instance;
if (!instance) {
instance = std::make_unique();
}
return *instance;
}
private:
Singleton() = default;
~Singleton() = default; // 可加清理逻辑
};
隐患:
- 如果程序异常退出(如
std::abort()),~Singleton()可能不被调用 - 多个单例之间若存在析构依赖,仍可能 crash(C++ 不保证静态对象析构顺序)
- 比 local static 写法多一次指针解引用,性能略低(通常可忽略)
双检锁(DCLP)在 C++ 中已过时且极易出错
早期为兼容 C++03 手写双重检查锁定(Double-Checked Locking Pattern),现在完全没必要。它要求:
- 用
std::atomic存储指针(裸指针 +volatile在 C++11 后无效) - 两次
load()都需指定内存序(如memory_order_acquire) - 中间
new操作必须确保构造完成后再发布指针,否则其他线程可能看到未初始化对象
哪怕抄对了,也比 local static 多出锁开销和复杂度。C++11 标准明确支持 local static 的线程安全初始化,这是标准给出的正解。
真正要注意的是:单例不是银弹。它的全局状态、隐式依赖和测试困难性,在大型项目中往往比实现本身更伤人。











