在多线程环境下,单例模式的实现需要保证即使多个线程同时请求实例时,也只会创建一次对象并返回同一实例。下面给出几种常用的线程安全实现方式,并说明其优缺点和使用场景。
1. C++11 std::call_once + std::once_flag
std::call_once 是标准库提供的原子性一次性执行函数,结合 std::once_flag 可以非常简洁地实现线程安全单例。
#include <iostream>
#include <memory>
#include <mutex>
class Singleton {
public:
static Singleton& getInstance() {
std::call_once(initFlag, []() {
instance.reset(new Singleton);
});
return *instance;
}
void sayHello() const { std::cout << "Hello from Singleton!\n"; }
private:
Singleton() { std::cout << "Singleton constructed\n"; }
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static std::unique_ptr <Singleton> instance;
static std::once_flag initFlag;
};
std::unique_ptr <Singleton> Singleton::instance;
std::once_flag Singleton::initFlag;
优点
- 代码简洁,使用标准库,线程安全保证可靠。
- 只在第一次调用时进行初始化,后续调用几乎没有开销。
缺点
- 需要 C++11 或更高版本。
2. Meyers 单例(函数内静态局部变量)
C++11 之后,函数内的局部静态变量在第一次使用时会被线程安全地初始化。
class Singleton {
public:
static Singleton& getInstance() {
static Singleton instance; // 线程安全初始化
return instance;
}
// 其它成员
private:
Singleton() { std::cout << "Singleton constructed\n"; }
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
优点
- 代码最简洁。
- 初始化时自动实现线程安全。
缺点
- 只能在 C++11 之后使用。
- 需要手动删除拷贝构造和赋值操作符,以防止意外复制。
3. 双重检查锁(Double-Checked Locking)
经典实现方式,适用于旧版本编译器或想完全控制锁的场景。
class Singleton {
public:
static Singleton* getInstance() {
if (instance == nullptr) {
std::lock_guard<std::mutex> lock(mtx);
if (instance == nullptr) {
instance = new Singleton();
}
}
return instance;
}
~Singleton() { delete instance; }
private:
Singleton() { std::cout << "Singleton constructed\n"; }
static Singleton* instance;
static std::mutex mtx;
};
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;
优点
- 对旧编译器友好,适用于不支持 C++11 的环境。
缺点
- 需要手动保证
instance的可见性,使用volatile或std::atomic。 - 代码较复杂,容易出现细节错误。
4. 静态局部变量 + std::atomic
如果你想在 C++03 环境下使用类似双重检查锁的方式,配合 std::atomic 可以实现:
#include <atomic>
class Singleton {
public:
static Singleton* getInstance() {
Singleton* tmp = instance.load(std::memory_order_acquire);
if (!tmp) {
std::lock_guard<std::mutex> lock(mtx);
tmp = instance.load(std::memory_order_relaxed);
if (!tmp) {
tmp = new Singleton();
instance.store(tmp, std::memory_order_release);
}
}
return tmp;
}
private:
Singleton() {}
static std::atomic<Singleton*> instance;
static std::mutex mtx;
};
std::atomic<Singleton*> Singleton::instance{nullptr};
std::mutex Singleton::mtx;
优点
- 兼容 C++03,使用标准原子操作。
缺点
- 代码量大,维护成本高。
5. 使用 std::shared_ptr 并配合 std::make_shared
如果你需要在多处共享单例且需要自动释放资源,使用 std::shared_ptr 也可以:
class Singleton {
public:
static std::shared_ptr <Singleton> getInstance() {
static std::shared_ptr <Singleton> instance = std::make_shared<Singleton>();
return instance;
}
private:
Singleton() {}
};
此方式与 Meyers 单例相似,C++11 后线程安全。
小结
| 方法 | 线程安全性 | 编译器要求 | 代码简洁度 | 适用场景 |
|---|---|---|---|---|
std::call_once + std::once_flag |
✔ | C++11+ | ✔ | 需要一次性初始化 |
| Meyers 单例 | ✔ | C++11+ | ✔ | 简洁、易用 |
| 双重检查锁 | ✔ | C++03+ | ⚠️ | 旧环境、需要手动锁 |
std::atomic 双检查 |
✔ | C++03+ | ⚠️ | 旧环境、细粒度控制 |
std::shared_ptr |
✔ | C++11+ | ✔ | 需要共享所有权 |
在现代 C++ 开发中,推荐使用 std::call_once 或 Meyers 单例,因为它们既简洁又安全,且完全依赖标准库实现。只有在特殊需求或兼容旧编译器时,才考虑使用更复杂的双重检查锁或 std::atomic 方案。