在 C++11 及之后的标准中,线程安全的单例实现变得异常简洁。最常见的方法是使用 std::call_once 或者直接利用函数内部静态变量的懒初始化特性。下面我们分别介绍这两种方案,并说明它们的原理、优点与适用场景。
1. 用 std::call_once 与 std::once_flag
#include <mutex>
#include <memory>
class Singleton {
public:
static Singleton& instance() {
std::call_once(flag_, [](){
ptr_.reset(new Singleton);
});
return *ptr_;
}
// 其他公共接口
void doSomething() { /* ... */ }
private:
Singleton() = default;
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static std::once_flag flag_;
static std::unique_ptr <Singleton> ptr_;
};
std::once_flag Singleton::flag_;
std::unique_ptr <Singleton> Singleton::ptr_;
优点
- 对多线程访问时,保证单例对象只会被构造一次。
std::call_once内部实现了必要的同步原语,使用更安全。
缺点
- 对象销毁时,
ptr_会在main结束时自动析构,除非你自己手动控制生命周期。
2. 用函数内部静态变量(C++11 之“线程安全的局部静态变量”)
class Singleton {
public:
static Singleton& instance() {
static Singleton instance; // C++11 guarantees thread-safe initialization
return instance;
}
void doSomething() { /* ... */ }
private:
Singleton() = default;
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
优点
- 代码最简洁。
- 语言层面保证线程安全。
- 对象的析构顺序与
main函数结束时全局对象的析构一致。
缺点
- 若程序在构造期间需要访问
Singleton,但此时单例尚未被构造,可能导致未定义行为。 - 在某些老旧编译器(C++03)中不保证线程安全。
3. 什么时候选择哪种实现?
| 场景 | 推荐实现 | 说明 |
|---|---|---|
需要手动控制销毁时机(如在 atexit 之前销毁) |
std::call_once + std::unique_ptr |
可以在需要时手动释放资源 |
| 简单单例,程序生命周期结束时即可销毁 | 函数内部静态变量 | 代码简洁,符合 C++11 标准 |
| 需要在多线程环境中保证首次初始化的绝对安全 | std::call_once |
兼容 C++11 之前的实现,或者对更细粒度控制有需求 |
4. 进阶:延迟加载与多线程环境下的性能考量
虽然 std::call_once 和静态局部变量都能保证线程安全,但在极高并发的场景下,初始化期间的锁竞争仍然是不可避免的。若单例对象构造成本极高,可考虑 双检锁(Double-Check Locking)方案,但需要注意内存屏障和编译器优化。C++11 标准下可以通过 std::atomic 与 std::memory_order 明确控制。
class LazySingleton {
public:
static LazySingleton* instance() {
LazySingleton* tmp = instance_.load(std::memory_order_acquire);
if (!tmp) {
std::lock_guard<std::mutex> lock(mutex_);
tmp = instance_.load(std::memory_order_relaxed);
if (!tmp) {
tmp = new LazySingleton;
instance_.store(tmp, std::memory_order_release);
}
}
return tmp;
}
private:
LazySingleton() = default;
static std::atomic<LazySingleton*> instance_;
static std::mutex mutex_;
};
std::atomic<LazySingleton*> LazySingleton::instance_{nullptr};
std::mutex LazySingleton::mutex_;
注意
- 必须保证
new之后的对象构造在store之前完成。 - 需要在程序结束时手动
delete对象,或者使用std::unique_ptr管理。
5. 小结
- C++11 提供了两种最常用的线程安全单例实现:
std::call_once与函数内部静态变量。 - 对于绝大多数应用,静态局部变量已经足够且最易维护。
- 若需手动控制生命周期或在旧编译器下兼容,可选择
std::call_once或双检锁实现。
掌握这两种模式后,你可以根据具体项目需求,灵活选择最合适的实现方式,实现既安全又高效的单例模式。