在 C++ 中实现单例(Singleton)模式是为了确保一个类只有一个实例,并提供全局访问点。对于多线程环境,最关键的是保证单例在并发访问时不会被多次实例化。下面提供几种常用且线程安全的实现方法,并说明其优缺点。
1. Meyer’s Singleton(局部静态变量)
class Singleton {
public:
static Singleton& instance() {
static Singleton instance; // C++11 之后的编译器保证线程安全
return instance;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() { /* 初始化代码 */ }
~Singleton() {}
};
-
优点
- 简单、易读。
- 延迟初始化:只有第一次访问时才会实例化。
- C++11 标准保证局部静态变量初始化线程安全。
-
缺点
- 在某些老旧编译器(C++03)或不符合标准的实现中不一定线程安全。
- 无法在实例化前进行自定义错误处理或日志。
2. 双重检查锁(Double-Checked Locking,DCL)
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;
}
// 其余成员同上
private:
Singleton() {}
~Singleton() {}
static std::atomic<Singleton*> instance_;
static std::mutex mutex_;
};
std::atomic<Singleton*> Singleton::instance_{nullptr};
std::mutex Singleton::mutex_;
-
优点
- 兼容 C++03,适用于不支持局部静态变量线程安全的编译器。
- 只在第一次初始化时加锁,后续访问无需锁,性能优越。
-
缺点
- 代码复杂,易出错。
- 需要使用
std::atomic与std::mutex,若不小心会出现“指令重排序”导致线程安全问题。
3. 静态 std::shared_ptr 与 std::call_once
class Singleton {
public:
static Singleton& instance() {
std::call_once(initFlag_, []() {
instancePtr_ = std::shared_ptr <Singleton>(new Singleton());
});
return *instancePtr_;
}
// 其余成员同上
private:
Singleton() {}
~Singleton() {}
static std::shared_ptr <Singleton> instancePtr_;
static std::once_flag initFlag_;
};
std::shared_ptr <Singleton> Singleton::instancePtr_{nullptr};
std::once_flag Singleton::initFlag_;
-
优点
- 采用
std::call_once保证初始化只执行一次,线程安全。 std::shared_ptr方便实现自毁(若需要在程序退出时释放资源)。- 代码结构清晰、易于维护。
- 采用
-
缺点
- 需要 C++11。
- 若想在程序退出时显式销毁实例,需要额外的控制逻辑。
4. 模板实现(更灵活)
如果你想在多处使用相同模式,可以用模板封装:
template <typename T>
class Singleton {
public:
static T& instance() {
static T inst;
return inst;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
protected:
Singleton() = default;
~Singleton() = default;
};
然后只需:
class MyService : public Singleton <MyService> {
friend class Singleton <MyService>; // 允许基类访问构造函数
MyService() { /* ... */ }
};
小结
- 推荐使用:如果使用 C++11 或更高版本,优先使用 Meyer’s Singleton,简单且线程安全。
- 兼容旧标准:若必须在 C++03 环境下工作,可选择双重检查锁或
std::call_once(需自实现)。 - 可扩展性:模板实现可在多个类中复用单例机制。
在实际项目中,请根据编译器、项目需求和团队技术栈选择合适的实现方式,并结合单元测试验证线程安全性。