在多线程环境下,单例模式需要保证只有一个实例,并且在多个线程并发访问时不产生竞态条件。C++11 引入了线程安全的静态局部变量初始化,利用这一特性可以轻松实现线程安全的单例。下面给出几种常见实现方式,并对它们的优缺点做简要比较。
1. Meyers单例(C++11 之后)
class Singleton {
public:
static Singleton& instance() {
static Singleton instance; // 线程安全初始化
return instance;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() {} // 私有构造
};
优点
- 代码最简洁,使用标准库即可。
- 编译器保证线程安全的初始化,性能优秀。
- 延迟加载:实例仅在第一次访问时创建。
缺点
- 不能在运行时销毁实例(如果需要在程序结束后释放资源,可在程序末尾手动调用
instance().~Singleton()或使用std::unique_ptr包装)。 - 需要 C++11 或更高版本。
2. 带互斥锁的懒汉式(适用于 C++11 之前)
#include <mutex>
class Singleton {
public:
static Singleton* getInstance() {
if (!instance_) {
std::lock_guard<std::mutex> lock(mutex_);
if (!instance_) {
instance_ = new Singleton();
}
}
return instance_;
}
~Singleton() { delete instance_; }
private:
Singleton() {}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton* instance_;
static std::mutex mutex_;
};
Singleton* Singleton::instance_ = nullptr;
std::mutex Singleton::mutex_;
优点
- 兼容旧版本 C++(如 C++03)。
- 可以手动控制实例的销毁。
缺点
- 双重检查锁(Double-Checked Locking)在某些编译器/平台上仍可能存在可见性问题。
- 额外的锁开销,即使实例已创建后仍需检查。
3. 使用 std::call_once
#include <mutex>
class Singleton {
public:
static Singleton& instance() {
std::call_once(initFlag_, []() { instance_ = new Singleton(); });
return *instance_;
}
~Singleton() { delete instance_; }
private:
Singleton() {}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton* instance_;
static std::once_flag initFlag_;
};
Singleton* Singleton::instance_ = nullptr;
std::once_flag Singleton::initFlag_;
优点
std::call_once保证一次性初始化,线程安全且效率高。- 与
std::mutex的双重检查相比更简洁。
缺点
- 需要手动销毁实例;若不销毁,可能在
atexit期间出现已销毁后仍被访问的问题。
4. 静态唯一实例(带手动销毁)
class Singleton {
public:
static Singleton& instance() {
static Singleton instance;
return instance;
}
static void destroy() {
// 必须先把引用计数降到0或手动销毁
}
private:
Singleton() {}
};
此方式适用于想在运行时销毁单例,但仍想保持线程安全。
总结
- 最推荐:如果使用 C++11 及以上,Meyers 单例是最简洁、性能最佳的选择。
- 兼容旧版:若项目需兼容 C++03,使用
std::mutex+ 双重检查或std::call_once(需手动实现)是可行方案。 - 需要手动销毁:使用
std::call_once或Meyers并在程序退出前手动销毁实例。
在实际项目中,建议先评估编译器支持、性能需求和资源管理需求,然后选用最合适的实现方式。