在多线程环境下,单例模式需要确保只有一个实例存在,并且在任何时刻都可以安全地访问该实例。下面给出几种常见的实现方式,并对其优缺点进行简要说明。
1. 局部静态变量(Meyers单例)
class Singleton {
public:
static Singleton& instance() {
static Singleton instance; // C++11之后的编译器保证线程安全
return instance;
}
// 禁止拷贝构造和赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
};
- 优点:实现简洁,编译器保证线程安全;延迟初始化(第一次调用时创建)。
- 缺点:无法控制实例的销毁时机(在程序退出时由系统负责)。
2. 双重检查锁(Double-Checked Locking)
class Singleton {
public:
static Singleton* instance() {
Singleton* tmp = instance_;
if (!tmp) {
std::lock_guard<std::mutex> lock(mutex_);
tmp = instance_;
if (!tmp) {
tmp = new Singleton;
instance_ = tmp;
}
}
return tmp;
}
// 禁止拷贝构造和赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
static std::atomic<Singleton*> instance_;
static std::mutex mutex_;
};
std::atomic<Singleton*> Singleton::instance_{nullptr};
std::mutex Singleton::mutex_;
- 优点:只有在首次创建实例时才加锁,后续访问更快。
- 缺点:实现复杂,易出错;需要 C++11 原子和内存序保证。
3. 静态成员指针 + 互斥量 + std::call_once
class Singleton {
public:
static Singleton& instance() {
std::call_once(flag_, []() {
instance_ = new Singleton;
});
return *instance_;
}
// 禁止拷贝构造和赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
static Singleton* instance_;
static std::once_flag flag_;
};
Singleton* Singleton::instance_ = nullptr;
std::once_flag Singleton::flag_;
- 优点:代码更安全,避免了手动锁;兼容 C++11 及以后。
- 缺点:需要手动管理实例的销毁,通常可通过
atexit或者std::unique_ptr自动释放。
4. 使用 std::shared_ptr 与 std::weak_ptr
如果单例需要按需销毁,可以使用 std::shared_ptr 与 std::weak_ptr 组合:
class Singleton {
public:
static std::shared_ptr <Singleton> instance() {
std::lock_guard<std::mutex> lock(mutex_);
if (auto sp = ptr_.lock()) {
return sp;
}
auto sp = std::shared_ptr <Singleton>(new Singleton);
ptr_ = sp;
return sp;
}
// 禁止拷贝构造和赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
static std::weak_ptr <Singleton> ptr_;
static std::mutex mutex_;
};
std::weak_ptr <Singleton> Singleton::ptr_;
std::mutex Singleton::mutex_;
- 优点:实例可被销毁后再次创建,资源更灵活。
- 缺点:实现更复杂,性能略低。
小结
- 对于大多数 C++11 及以后项目,Meyers单例(局部静态变量)已足够,代码简洁且线程安全。
- 若需要更细粒度的控制或想延迟销毁,推荐使用
std::call_once或std::shared_ptr方案。 - 双重检查锁虽然理论上更快,但在 C++11 的内存模型下实现复杂且不推荐使用。
选择合适的实现方式,既能保证线程安全,又能满足项目的资源管理需求。