【设计模式】C++ 单例模式总结与最佳实践
1. 单例模式简介
单例模式(Singleton Pattern) 是软件开发中常见的设计模式之一,主要用于 确保某个类只有一个实例,并提供一个全局访问点。常见的使用场景包括:
- 日志管理:全局唯一的日志记录器。
- 数据库连接池:防止创建多个数据库连接,提高性能。
- 资源管理器:如线程池、驱动管理器等。
2. 单例模式的实现方式
C++ 中实现单例模式的方式有多种,常见方式如下:
2.1 普通的单例模式(非线程安全)
特点:
- 使用静态成员变量存储唯一实例。
- 懒加载(Lazy Initialization),即 首次访问时创建实例。
- 非线程安全,在多线程环境下可能创建多个实例。
#include <iostream>
class Singleton {
private:
static Singleton* instance;
Singleton() { std::cout << "Singleton Instance Created\n"; }
public:
static Singleton* getInstance() {
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
};
// 初始化静态成员变量
Singleton* Singleton::instance = nullptr;
int main() {
Singleton* s1 = Singleton::getInstance();
Singleton* s2 = Singleton::getInstance();
std::cout << (s1 == s2) << "\n"; // 输出 1,表示同一个实例
}
缺点:
- 非线程安全:多个线程可能同时调用
getInstance()
,导致创建多个实例。 - 内存泄漏:实例在程序结束时不会自动释放。
2.2 线程安全的单例模式
方法 1:使用互斥锁(Mutex)
特点:
- 通过
std::mutex
保护getInstance()
,确保多线程安全。 - 缺点:每次获取实例都要加锁,影响性能。
#include <iostream>
#include <mutex>
class Singleton {
private:
static Singleton* instance;
static std::mutex mutex;
Singleton() { std::cout << "Singleton Instance Created\n"; }
public:
static Singleton* getInstance() {
std::lock_guard<std::mutex> lock(mutex);
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
};
// 初始化静态变量
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex;
方法 2:双重检查锁(DCLP)
特点:
- 提高性能:只有在
instance == nullptr
时才会加锁。 - C++11 及以后推荐使用,早期 C++ 需要
volatile
避免指令重排。
#include <iostream>
#include <mutex>
class Singleton {
private:
static Singleton* instance;
static std::mutex mutex;
Singleton() {}
public:
static Singleton* getInstance() {
if (instance == nullptr) { // 第一次检查
std::lock_guard<std::mutex> lock(mutex);
if (instance == nullptr) { // 第二次检查
instance = new Singleton();
}
}
return instance;
}
};
// 初始化静态变量
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mutex;
2.3 C++11 静态局部变量单例(推荐)
特点:
- C++11 保证静态局部变量初始化是线程安全的,不需要
mutex
。 - 代码简洁,性能最佳,生命周期由编译器自动管理。
- 唯一缺点:不能提前释放实例,程序结束时才释放。
#include <iostream>
class Singleton {
private:
Singleton() { std::cout << "Singleton Instance Created\n"; }
public:
static Singleton& getInstance() {
static Singleton instance; // C++11 线程安全
return instance;
}
};
int main() {
Singleton& s1 = Singleton::getInstance();
Singleton& s2 = Singleton::getInstance();
std::cout << (&s1 == &s2) << "\n"; // 输出 1
}
3. 各种单例模式对比
实现方式 | 线程安全 | 性能 | 代码复杂度 | 生命周期管理 |
---|---|---|---|---|
普通懒汉式 | ❌ 否 | ⭐⭐⭐ | ⭐⭐⭐ | 手动释放 |
加锁(mutex) | ✅ 是 | ⭐⭐ | ⭐⭐⭐ | 手动释放 |
双重检查锁(DCLP) | ✅ 是 | ⭐⭐⭐ | ⭐⭐⭐⭐ | 手动释放 |
静态局部变量(推荐) | ✅ 是 | ⭐⭐⭐⭐⭐ | ⭐ | 自动释放 |
4. 结论:推荐使用 C++11 静态局部变量单例
如果你的 C++ 编译器支持 C++11 及以上,推荐使用静态局部变量单例:
- 线程安全 ✅
- 性能最佳 ✅
- 代码简洁 ✅
- 自动管理生命周期 ✅
适用场景
场景 | 适用单例方式 |
---|---|
普通 C++ 项目 | C++11 静态局部变量 |
多线程项目 | C++11 静态局部变量 / DCLP |
C++03 及以下 | 互斥锁(mutex) |
如果需要手动管理对象生命周期
可以使用 std::unique_ptr
或 std::shared_ptr
:
#include <memory>
class Singleton {
public:
static std::shared_ptr<Singleton> getInstance() {
static std::shared_ptr<Singleton> instance(new Singleton());
return instance;
}
};
5. 总结
C++ 单例模式有多种实现方式,C++11 之后,静态局部变量单例是最佳选择,它具备线程安全性、简洁性和性能优势。如果你在 旧版 C++ 或 MFC 中使用,则需要 手动加锁(mutex) 或 双重检查锁(DCLP)。
对于 MFC 开发,可以用 CCriticalSection
替代 std::mutex
,确保兼容性。
你的选择?
你的项目用的是 C++11 以上吗?如果是,直接用 静态局部变量!
如果你还在用 C++03,那就用 互斥锁(mutex) 或 DCLP!