在 C++11 及以后版本中,静态局部变量的初始化是线程安全的,这使得实现线程安全的单例变得异常简单。下面演示两种常见实现方式,并说明它们的优缺点。
1. Meyers 单例(局部静态变量)
class Singleton {
public:
static Singleton& getInstance() {
static Singleton instance; // 线程安全的局部静态变量
return instance;
}
// 禁止拷贝构造和赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() = default;
~Singleton() = default;
};
关键点
| 关键点 |
说明 |
| 局部静态变量 |
C++11 起保证初始化线程安全,多个线程首次访问时只会有一次构造。 |
| 延迟初始化 |
只有第一次调用 getInstance() 时才会构造,符合“按需加载”。 |
| 易于维护 |
代码最短,几乎不需要手动管理互斥量。 |
注意事项
- 全局析构:若实例的析构需要在程序结束时执行,确保没有悬挂引用。C++11 标准保证在
main 结束后销毁局部静态变量。
- 多线程测试:在高并发环境下,仍需验证是否有死锁或资源竞争,虽然标准保证安全,但编译器实现必须符合规范。
2. 双重检查锁(Double-Check Locking, DCL)
class Singleton {
public:
static Singleton* getInstance() {
if (instance_ == nullptr) {
std::lock_guard<std::mutex> lock(mutex_);
if (instance_ == nullptr) {
instance_ = new Singleton();
}
}
return instance_;
}
// 删除拷贝构造与赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
~Singleton() { delete instance_; }
private:
Singleton() = default;
static Singleton* instance_;
static std::mutex mutex_;
};
Singleton* Singleton::instance_ = nullptr;
std::mutex Singleton::mutex_;
关键点
| 关键点 |
说明 |
| 双重检查 |
先快速检查实例是否已存在,减少锁竞争。 |
| 显式互斥 |
使用 std::mutex 保护实例创建过程。 |
| 手动析构 |
需要手动删除实例,或使用 std::unique_ptr 自动管理。 |
缺点
- 实现复杂:代码量大,容易出现错误(如忘记
volatile 或指令重排导致的问题)。
- 不推荐:自 C++11 起
std::call_once 或局部静态变量更安全、更简洁。
3. 使用 std::call_once
class Singleton {
public:
static Singleton& getInstance() {
std::call_once(flag_, []() {
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 flag_;
};
Singleton* Singleton::instance_ = nullptr;
std::once_flag Singleton::flag_;
关键点
std::once_flag:保证闭包只执行一次,即使多线程并发访问。
- 简洁性:相比 DCL,代码更短,易于维护。
4. 何时选用哪种实现?
| 场景 |
推荐实现 |
| 简单项目 |
Meyers 单例(局部静态变量) |
| 需要手动析构 |
std::call_once + 手动 delete |
| 兼容旧标准(C++03) |
双重检查锁 + pthread_once 或 std::call_once(Boost) |
| 线程安全 + 延迟初始化 |
所有实现均满足,选择最简洁的即可 |
5. 小结
- C++11 引入的线程安全的局部静态变量让单例实现变得无比简单和可靠。
- 传统的双重检查锁实现虽然可行,但更易出错,且已被更现代、更安全的
std::call_once 或局部静态变量所取代。
- 选用何种实现取决于项目对构造/析构时机、代码简洁性以及对旧标准的兼容需求。
如需进一步了解 C++ 中的多线程同步机制,可继续探讨 std::mutex, std::shared_mutex, std::atomic 等工具。