在多线程环境下,单例模式的实现需要保证同一时刻只有一个实例被创建,并且在所有线程中共享该实例。下面给出几种常见的实现方式,并结合 C++17 及其后的标准进行说明。
1. 静态局部变量 + std::call_once
C++11 以后,局部静态变量的初始化是线程安全的,但如果想更显式地控制一次性初始化,可以配合 std::call_once 使用。
#include <mutex>
class Singleton {
public:
static Singleton& instance() {
std::call_once(initFlag_, []() {
instance_.reset(new Singleton);
});
return *instance_;
}
// 业务接口
void doSomething() { /* ... */ }
private:
Singleton() = default;
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static std::unique_ptr <Singleton> instance_;
static std::once_flag initFlag_;
};
std::unique_ptr <Singleton> Singleton::instance_ = nullptr;
std::once_flag Singleton::initFlag_;
优点
- 明确一次性初始化的语义。
std::call_once只在第一次调用时执行一次,后续调用几乎无开销。
2. 带懒加载的局部静态变量
C++11 之后,局部静态变量的初始化是线程安全的。利用这一特性可以写出最简洁的单例。
class Singleton {
public:
static Singleton& instance() {
static Singleton instance; // 线程安全的懒加载
return instance;
}
void doSomething() { /* ... */ }
private:
Singleton() = default;
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
优点
- 代码最短,易于阅读。
- 只需要一次内存分配,且初始化完成后不需要额外同步。
3. Meyers 单例(C++11 版)
Meyers 单例就是上述第二种实现方式的命名,因其由 Scott Meyers 提倡而得名。它利用局部静态变量实现懒加载和线程安全,已经成为 C++ 社区的标准做法。
4. 双重检查锁(Double-Check Locking)
虽然在 C++11 之前使用双重检查锁能实现线程安全单例,但在现代 C++ 中已不推荐使用。原因是它的实现依赖于内存模型的细节,容易出错。
class Singleton {
public:
static Singleton* instance() {
if (!instance_) {
std::lock_guard<std::mutex> lock(mutex_);
if (!instance_) {
instance_ = new Singleton();
}
}
return instance_;
}
private:
static Singleton* instance_;
static std::mutex mutex_;
};
注意
- 必须保证
instance_的初始化对所有线程可见。 - 如果不使用
volatile或std::atomic,可能出现指针可见性问题。
5. 预先初始化(Eager Initialization)
如果实例创建成本低且不需要延迟加载,可以直接在静态成员中初始化。
class Singleton {
public:
static Singleton& instance() {
return instance_;
}
private:
static Singleton instance_;
};
Singleton Singleton::instance_;
优点
- 简单直观。
- 对于构造函数不抛异常的类非常安全。
6. 线程安全的惰性加载与销毁
如果需要在程序结束时显式销毁单例,可结合 std::unique_ptr 与 std::atexit:
class Singleton {
public:
static Singleton& instance() {
std::call_once(initFlag_, []() {
ptr_.reset(new Singleton);
std::atexit(&Singleton::destroy);
});
return *ptr_;
}
private:
static void destroy() { ptr_.reset(); }
static std::unique_ptr <Singleton> ptr_;
static std::once_flag initFlag_;
};
这样可以确保单例在程序退出前被正确销毁,避免潜在的资源泄漏。
小结
- 最推荐:C++11 之后使用局部静态变量实现的 Meyers 单例,既简洁又线程安全。
- 若需要更细粒度的控制或日志,可使用
std::call_once。 - 避免双重检查锁和手动同步,除非你必须兼容旧标准或有特殊性能需求。
选择哪种实现方式,取决于项目的需求、编译器支持以及对代码可读性的要求。祝你编码愉快!