在多线程环境下,单例模式的实现需要保证同一时刻只有一个线程可以创建实例。最常用的方法是使用 C++11 的静态局部变量,它在首次调用时会以线程安全的方式进行初始化。另外,也可以使用双重检查锁定(Double-Check Locking)配合 std::atomic 或者 std::call_once 来实现。下面分别给出两种实现方式,并对其优缺点进行分析。
1. 基于 C++11 静态局部变量的实现
class Singleton {
public:
static Singleton& getInstance() {
static Singleton instance; // C++11 规定在第一次调用时线程安全初始化
return instance;
}
// 禁止拷贝和移动构造
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
Singleton(Singleton&&) = delete;
Singleton& operator=(Singleton&&) = delete;
private:
Singleton() { /* 可能的资源初始化 */ }
~Singleton() { /* 资源释放 */ }
};
优点
- 代码简洁,编译器负责实现线程安全。
- 初始化一次后,后续访问几乎没有开销。
缺点
- 如果
Singleton的构造函数抛异常,程序将无法恢复。 - 不能在程序结束前显式销毁实例(除非使用
std::unique_ptr结合std::shared_ptr的自定义删除器)。
2. 基于 std::call_once 的实现
#include <mutex>
class Singleton {
public:
static Singleton& getInstance() {
std::call_once(initFlag, [](){ instance.reset(new Singleton()); });
return *instance;
}
// 禁止拷贝和移动
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
Singleton(Singleton&&) = delete;
Singleton& operator=(Singleton&&) = delete;
private:
Singleton() {}
~Singleton() {}
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可以保证初始化代码只执行一次,且异常安全。- 可以在需要时显式销毁实例(例如
instance.reset())。
缺点
- 需要手动维护指针和销毁逻辑,代码稍显复杂。
3. 双重检查锁定(Double-Check Locking)
虽然在 C++11 之前的实现常见,但在现代 C++ 中不再推荐,因为 std::atomic 不能保证对对象完整性的可见性。示例代码仅供参考。
class Singleton {
public:
static Singleton* getInstance() {
Singleton* tmp = instance.load(std::memory_order_acquire);
if (!tmp) {
std::lock_guard<std::mutex> lock(mtx);
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() {}
static std::atomic<Singleton*> instance;
static std::mutex mtx;
};
std::atomic<Singleton*> Singleton::instance{nullptr};
std::mutex Singleton::mtx;
优点
- 仅在首次创建实例时产生锁开销。
缺点
- 需要手动保证内存可见性,容易出错。
- 现代 C++ 提供了更安全、更简洁的方案。
小结
在 C++11 及以后,推荐使用 静态局部变量 或 std::call_once 来实现线程安全的单例。前者最简洁,后者在异常安全和显式销毁方面更有优势。双重检查锁定已被视为过时实践,除非有极端性能要求且已在细节上做足够保证,否则不建议使用。
通过上述三种实现方式,你可以根据项目需求、异常处理策略和资源释放时机,选择最合适的单例实现方式。