在多线程环境下,单例模式的实现必须确保只有一个实例被创建,同时不产生竞争条件。下面给出几种常用且线程安全的实现方式,并比较其优缺点。
1. C++11 std::call_once + std::once_flag
#include <mutex>
class Singleton {
public:
static Singleton& instance() {
std::call_once(initFlag_, []() {
instance_ = new Singleton();
});
return *instance_;
}
// 禁止拷贝和移动
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
~Singleton() = default;
static Singleton* instance_;
static std::once_flag initFlag_;
};
Singleton* Singleton::instance_ = nullptr;
std::once_flag Singleton::initFlag_;
- 优点:实现简单,利用了 C++11 标准库的原子操作,保证初始化仅执行一次。
- 缺点:需要手动管理单例对象的销毁,若不加注意可能导致内存泄漏。
2. 局部静态变量(Meyers Singleton)
class Singleton {
public:
static Singleton& instance() {
static Singleton instance; // 函数内局部静态变量
return instance;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
~Singleton() = default;
};
- 优点:编译器负责初始化和销毁,代码极为简洁。自 C++11 起,局部静态变量的初始化已保证线程安全。
- 缺点:若需要在程序结束前手动销毁实例(例如为释放资源),需要额外实现。
3. 双重检查锁(Double-Checked Locking)
#include <atomic>
#include <mutex>
class Singleton {
public:
static Singleton* instance() {
Singleton* 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 Singleton();
instance_.store(tmp, std::memory_order_release);
}
}
return tmp;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
~Singleton() = default;
static std::atomic<Singleton*> instance_;
static std::mutex mutex_;
};
std::atomic<Singleton*> Singleton::instance_{nullptr};
std::mutex Singleton::mutex_;
- 优点:只在首次创建时加锁,后续访问几乎无锁,性能更好。
- 缺点:实现复杂,容易出错;若忘记使用
memory_order,在某些编译器上可能出现数据竞争。
4. 静态局部对象 + std::unique_ptr
如果想在销毁时控制顺序,可结合 std::unique_ptr:
class Singleton {
public:
static Singleton& instance() {
static std::unique_ptr <Singleton> instance(new Singleton());
return *instance;
}
// ...
};
- 优点:确保单例在程序退出时被正确析构,且析构顺序与其他
static对象一致。 - 缺点:与前面方法相同,代码稍显冗长。
选择哪种实现?
| 方案 | 线程安全性 | 代码复杂度 | 对销毁的控制 | 适用场景 |
|---|---|---|---|---|
std::call_once |
✅ | 低 | 手动 | 需要手动销毁或延迟销毁 |
| 局部静态(Meyers) | ✅ (C++11+) | 低 | 自动 | 简洁,常见做法 |
| 双重检查锁 | ✅ | 高 | 手动 | 性能极致需求 |
unique_ptr + 静态 |
✅ | 低 | 自动 | 需要控制析构顺序 |
在大多数情况下,局部静态变量(Meyers Singleton) 已经足够满足需求,代码最简洁且符合标准。若对线程安全有更细致的需求或需要在销毁时做特殊处理,可考虑 std::call_once 或 unique_ptr 版本。若性能至上且能接受复杂实现,双重检查锁也是一个可选方案。