C++ 单例模式的实现方式多样,核心在于确保一个类仅有一个实例,并提供全局访问点。以下是常见实现及其线程安全性分析:
1. 饿汉模式(Eager Initialization)
特点:
- 类加载时立即实例化,线程安全(由静态初始化保证)。
- 简单直接,但可能导致资源浪费(若实例未被使用)。
实现代码:
class EagerSingleton {
private:
static EagerSingleton instance; // 静态实例(类加载时初始化)
EagerSingleton() = default; // 私有构造函数
~EagerSingleton() = default;
EagerSingleton(const EagerSingleton&) = delete; // 禁用拷贝
EagerSingleton& operator=(const EagerSingleton&) = delete; // 禁用赋值
public:
static EagerSingleton& getInstance() {
return instance;
}
};
// 静态实例初始化(全局作用域)
EagerSingleton EagerSingleton::instance;
线程安全性:
✅ 线程安全。静态变量的初始化在程序启动时完成,且 C++ 标准保证静态变量初始化的线程安全性。
2. 懒汉模式(Lazy Initialization)- 非线程安全
特点:
- 首次调用
getInstance()时实例化,延迟初始化。 - 非线程安全,多线程环境下可能创建多个实例。
实现代码:
class LazySingleton {
private:
static LazySingleton* instance; // 静态指针(初始化为nullptr)
LazySingleton() = default;
~LazySingleton() = default;
LazySingleton(const LazySingleton&) = delete;
LazySingleton& operator=(const LazySingleton&) = delete;
public:
static LazySingleton* getInstance() {
if (instance == nullptr) { // 非线程安全:多线程可能同时通过此检查
instance = new LazySingleton();
}
return instance;
}
};
// 初始化为nullptr
LazySingleton* LazySingleton::instance = nullptr;
线程安全性:
❌ 非线程安全。多线程环境下,若多个线程同时进入if (instance == nullptr)判断,可能创建多个实例。
3. 懒汉模式 - 线程安全(同步锁)
特点:
- 通过互斥锁(
std::mutex)保证线程安全,但每次调用getInstance()都需加锁,性能开销大。
实现代码:
#include <mutex>
class ThreadSafeSingleton {
private:
static ThreadSafeSingleton* instance;
static std::mutex mutex_; // 互斥锁
ThreadSafeSingleton() = default;
~ThreadSafeSingleton() = default;
ThreadSafeSingleton(const ThreadSafeSingleton&) = delete;
ThreadSafeSingleton& operator=(const ThreadSafeSingleton&) = delete;
public:
static ThreadSafeSingleton* getInstance() {
std::lock_guard<std::mutex> lock(mutex_); // 加锁
if (instance == nullptr) {
instance = new ThreadSafeSingleton();
}
return instance;
}
};
ThreadSafeSingleton* ThreadSafeSingleton::instance = nullptr;
std::mutex ThreadSafeSingleton::mutex_;
线程安全性:
✅ 线程安全。通过互斥锁保证实例创建的原子性,但锁的开销影响性能。
4. 双重检查锁定(Double-Checked Locking)
特点:
- 减少锁的使用频率,仅在实例未创建时加锁,兼顾性能与安全。
- 需要处理内存可见性问题(C++11 及以后通过
std::atomic解决)。
实现代码(C++11 及以后):
#include <mutex>
#include <atomic>
class DoubleCheckedSingleton {
private:
static std::atomic<DoubleCheckedSingleton*> instance; // 原子指针
static std::mutex mutex_;
DoubleCheckedSingleton() = default;
~DoubleCheckedSingleton() = default;
DoubleCheckedSingleton(const DoubleCheckedSingleton&) = delete;
DoubleCheckedSingleton& operator=(const DoubleCheckedSingleton&) = delete;
public:
static DoubleCheckedSingleton* getInstance() {
DoubleCheckedSingleton* tmp = instance.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire); // 内存屏障
if (tmp == nullptr) {
std::lock_guard<std::mutex> lock(mutex_);
tmp = instance.load(std::memory_order_relaxed);
if (tmp == nullptr) {
tmp = new DoubleCheckedSingleton();
std::atomic_thread_fence(std::memory_order_release); // 内存屏障
instance.store(tmp, std::memory_order_relaxed);
}
}
return tmp;
}
};
std::atomic<DoubleCheckedSingleton*> DoubleCheckedSingleton::instance(nullptr);
std::mutex DoubleCheckedSingleton::mutex_;
线程安全性:
✅ 线程安全。通过原子操作和内存屏障确保多线程环境下实例创建的原子性和可见性。
5. Magic Statics(C++11 及以后)
特点:
- 利用 C++11 的静态局部变量特性,自动保证线程安全和延迟初始化。
- 实现最简单,推荐使用。
实现代码:
class MagicSingleton {
private:
MagicSingleton() = default;
~MagicSingleton() = default;
MagicSingleton(const MagicSingleton&) = delete;
MagicSingleton& operator=(const MagicSingleton&) = delete;
public:
static MagicSingleton& getInstance() {
static MagicSingleton instance; // C++11保证线程安全的局部静态变量
return instance;
}
};
线程安全性:
✅ 线程安全。C++11 标准规定,静态局部变量的初始化在多线程环境下是原子的(每个线程仅初始化一次)。
总结对比
| 实现方式 | 线程安全性 | 延迟初始化 | 适用场景 |
|---|---|---|---|
| 饿汉模式 | ✅ 安全 | ❌ 否 | 实例创建开销小,必被使用 |
| 懒汉模式(非线程安全) | ❌ 不安全 | ✅ 是 | 单线程环境 |
| 懒汉模式(同步锁) | ✅ 安全 | ✅ 是 | 多线程,性能要求不高 |
| 双重检查锁定 | ✅ 安全 | ✅ 是 | 多线程,高性能要求 |
| Magic Statics(C++11+) | ✅ 安全 | ✅ 是 | 多线程,代码简洁性优先 |
注意事项
- 内存泄漏:手动
new创建的实例需手动delete(或使用智能指针管理),而 Magic Statics 和饿汉模式的静态实例会自动释放。 - 继承与多态:单例模式通常不支持继承,若需要多态,可考虑工厂模式或依赖注入。
- 跨 DLL 问题:在 Windows DLL 中使用单例需注意实例可能在不同模块中重复创建,Linux 下无此问题。
选择实现方式时,优先考虑 C++11 的 Magic Statics,简洁且安全;若需兼容旧版本,可使用双重检查锁定。
1053

被折叠的 条评论
为什么被折叠?



