单例模式(Singleton)是一种常用的设计模式,用来保证一个类只有一个实例,并提供一个全局访问点。随着多线程程序的普及,传统的单例实现往往会出现并发安全问题。下面给出几种在 C++11 及以后版本中实现线程安全单例的方式,并讨论各自的优缺点。
1. 局部静态变量(Meyers 单例)
class Singleton {
public:
static Singleton& instance() {
static Singleton inst; // C++11 规定此处初始化是线程安全的
return inst;
}
// 删除拷贝构造和赋值运算符
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() { /* 资源初始化 */ }
~Singleton() { /* 资源释放 */ }
};
优点
- 代码最简洁。
- 只需一次初始化,且在第一次使用时才会创建实例,天然懒加载。
- C++11 标准保证了线程安全的初始化。
缺点
- 无法在程序退出时确定析构顺序,可能导致 “静态销毁顺序问题”。
- 需要在函数内部定义静态对象,若想控制实例生命周期(如显式销毁)则不方便。
2. std::call_once + std::unique_ptr
class Singleton {
public:
static Singleton& instance() {
std::call_once(initFlag, []{
instancePtr.reset(new Singleton);
});
return *instancePtr;
}
private:
Singleton() {}
~Singleton() {}
static std::unique_ptr <Singleton> instancePtr;
static std::once_flag initFlag;
};
std::unique_ptr <Singleton> Singleton::instancePtr;
std::once_flag Singleton::initFlag;
优点
- 通过
std::once_flag明确表示只执行一次。 - 使用
unique_ptr可以更灵活地控制析构顺序(可在程序中手动销毁)。 - 与
Meyers单例相比,避免了静态对象初始化时的潜在“静态销毁顺序问题”。
缺点
- 代码稍微繁琐。
- 仍需手动删除实例,可能导致内存泄漏风险。
3. 双重检查锁(Double-Check Locking)
class Singleton {
public:
static Singleton* instance() {
Singleton* tmp = instancePtr;
if (tmp == nullptr) {
std::lock_guard<std::mutex> lock(mtx);
if (instancePtr == nullptr) {
instancePtr = new Singleton;
}
tmp = instancePtr;
}
return tmp;
}
private:
Singleton() {}
~Singleton() {}
static Singleton* instancePtr;
static std::mutex mtx;
};
Singleton* Singleton::instancePtr = nullptr;
std::mutex Singleton::mtx;
优点
- 实现时不依赖 C++11 的线程安全静态变量,兼容老版本。
- 只在第一次创建实例时锁,后续访问不需要锁,性能更好。
缺点
- 实现错误率高,需要保证
instancePtr采用std::atomic<Singleton*>或使用内存序列化。 - 对于 C++11 及之后的标准,
Meyers单例已足够安全且更简洁,双重检查锁不再推荐。
4. 经典单例类(静态成员+构造函数私有化)
class Singleton {
public:
static Singleton& getInstance() {
return *instance;
}
private:
Singleton() {}
~Singleton() {}
static Singleton* instance;
};
Singleton* Singleton::instance = new Singleton;
优点
- 传统实现,易于理解。
缺点
- 静态成员在程序结束前一直存在,无法控制销毁顺序。
- 不是懒加载,实例在程序启动时即创建。
- 线程不安全,需自行加锁。
小结
- 推荐:使用 C++11 之后的局部静态变量实现(Meyers 单例)。其代码最简洁、性能最佳,并且标准已保证线程安全。
- 需要显式销毁:可以结合
std::call_once+unique_ptr的实现,手动管理生命周期。 - 兼容旧标准:如果项目必须在 C++98/03 上编译,可使用双重检查锁(但务必使用
volatile/atomic并保证内存序列化)。
掌握上述实现方式后,你可以根据项目需求选择最合适的单例实现,并在多线程环境下保持代码的安全性与可维护性。