单例模式(Singleton Pattern)是一种常见的软件设计模式,旨在保证一个类只有一个实例,并提供全局访问点。在多线程环境下,如何保证该实例在并发情况下仅被创建一次,成为实现线程安全单例的关键。下面介绍几种在C++11及以后版本中实现线程安全单例的常用方法,并讨论其优缺点。
1. 局部静态变量(Meyers 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() {} // 私有构造
};
优点
- 代码简洁,易于理解。
- C++11 标准保证局部静态变量在第一次使用时线程安全地初始化。
- 资源释放由系统在程序结束时自动处理,避免手动销毁导致的生命周期问题。
缺点
- 对于需要按需销毁实例的场景(如需要在程序某个阶段销毁单例),无法控制生命周期。
- 在某些编译器或环境下,可能存在性能隐患(每次访问时检查实例是否已初始化)。
2. 带双重检查锁定(Double-Checked Locking) + std::atomic
#include <atomic>
#include <mutex>
class Singleton {
public:
static Singleton* instance() {
Singleton* tmp = instance_.load(std::memory_order_acquire);
if (!tmp) {
std::lock_guard<std::mutex> lock(mutex_);
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 mutex_;
};
std::atomic<Singleton*> Singleton::instance_{nullptr};
std::mutex Singleton::mutex_;
优点
- 可以在运行时控制实例的创建和销毁。
- 对于只需要一次实例化且随后多次访问的情况,锁的开销被极大降低。
缺点
- 代码相对复杂,易出错。
- 需要手动管理内存,若未正确销毁会导致内存泄漏。
3. std::call_once 与 std::once_flag
#include <mutex>
class Singleton {
public:
static Singleton& instance() {
std::call_once(flag_, [](){ instance_.reset(new Singleton); });
return *instance_;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() {}
static std::unique_ptr <Singleton> instance_;
static std::once_flag flag_;
};
std::unique_ptr <Singleton> Singleton::instance_;
std::once_flag Singleton::flag_;
优点
- 代码简洁,使用标准库提供的
call_once机制,天然线程安全。 once_flag的实现已针对多核处理器做过优化,性能优秀。
缺点
- 与局部静态变量类似,实例生命周期由程序结束时释放,无法显式销毁。
4. 模板实现,支持多种实例化方式
template <typename T>
class Singleton {
public:
static T& instance() {
static T instance;
return instance;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
protected:
Singleton() = default;
~Singleton() = default;
};
使用时只需 `Singleton
mySingleton;`。这种方式可复用单例实现,减少代码重复。 ### 5. 小结 – **最简单、推荐**:C++11 的局部静态变量(Meyers Singleton),几行代码即可实现线程安全单例,除非需要手动销毁实例,否则几乎不需要额外维护。 – **需要显式销毁**:使用 `std::call_once` 与 `std::unique_ptr`,既安全又可控制生命周期。 – **高性能需求**:双重检查锁定配合 `std::atomic` 可进一步减少锁开销,但实现更复杂。 在实际项目中,通常建议使用 `std::call_once` 或局部静态变量实现单例。这样既能保证线程安全,又能保持代码可维护性,避免因多线程实现不当导致的难以调试的问题。