一、单例模式
1.1、核心思想
保证一个类只有一个实例,并提供一个全局访问点
1.2、为什么要用单例模式?
问题场景:
假设有一个配置管理器,如果程序中到处都能随意创建它的实例:
cpp
// 在A文件中
ConfigManager* config1 = new ConfigManager();
config1->setValue("theme", "dark");
// 在B文件中
ConfigManager* config2 = new ConfigManager();
string theme = config2->getValue("theme"); // 可能得到错误的值!
配置信息不一致,内存浪费。
解决方案:单例模式
cpp
// 在A文件中
ConfigManager* config1 = ConfigManager::getInstance();
config1->setValue("theme", "dark");
// 在B文件中
ConfigManager* config2 = ConfigManager::getInstance();
string theme = config2->getValue("theme"); // 保证是 "dark"
// config1 和 config2 实际上是同一个实例!
1.3、单例模式实现
- 基础懒汉式(线程不安全)
cpp
class Singleton {
private:
static Singleton* instance; // 静态私有成员
Singleton() {} // 私有构造函数,防止外部创建实例
Singleton(const Singleton&) = delete; // 禁止拷贝
Singleton& operator=(const Singleton&) = delete; // 禁止赋值
public:
static Singleton* getInstance() {
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
void doSomething() {
cout << "单例对象在工作..." << endl;
}
};
// 静态成员初始化
Singleton* Singleton::instance = nullptr;
- 懒汉式(线程安全,但不高效)
cpp
#include <mutex>
class Singleton {
private:
static Singleton* instance;
static std::mutex mtx; // 互斥锁
Singleton() {
cout << "Singleton 实例被创建!" << endl;
}
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
static Singleton* getInstance() {
if (instance == nullptr) { // 第一次检查,避免不必要的锁
std::lock_guard<std::mutex> lock(mtx); // 加锁
if (instance == nullptr) { // 第二次检查,确保线程安全
instance = new Singleton();
}
}
return instance;
}
void doSomething() {
cout << "单例对象在工作..." << endl;
}
};
// 静态成员初始化
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;
利用互斥锁进行线程安全控制,但每次调用getInstance()时都需要加锁解锁,效率较低。
- C++11 静态局部变量(线程安全,高效)
cpp
class Singleton {
private:
Singleton() = default;
~Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
static Singleton& getInstance() {
static Singleton instance; // C++11保证这是线程安全的
return instance;
}
void doSomething() {
cout << "单例对象在工作..." << endl;
}
};
线程安全:C++11标准保证
二、实战案例
获取系统时间:
很明确这是全局且唯一的,那么可以使用单例模式。
cpp
class SystemTime
{
public:
static SystemTime* getInstance()
{
static SystemTime instance;
return &instance;
}
~ SystemTime(){}
DateTime getCurrentTime()const{ return DateTime::now(); }
private:
SystemTime(){}
SystemTime(const SystemTime&)=delete;
SystemTime& operator=(const SystemTime&)=delete;
};
// 使用
auto currentTime = SystemTime::getInstance()->getCurrentTime();
不过实际应用中,时间一般都包含在某个组件中,比如日志,游戏存档等,所以一般不会单独用一个单例类来管理时间。
三、总结
3.1、单例模式优缺点
| 优点 | 缺点 |
|---|---|
| 严格控制实例数量 | 全局状态,可能被任意修改 |
| 全局访问点,方便使用 | 不利于单元测试 |
| 节约系统资源 | 违反单一职责原则(既要管理业务,也要管理生命周期) |
| 懒加载,提高性能 | 隐藏了依赖关系 |
3.2、适用场景
- 日志系统:整个程序共享一个日志实例
- 数据库连接池:只需要一个连接池管理器
- 线程池
- 游戏存档系统
***Tips:***没有完美的设计模式,只有更适合该场景的模式;在使用单例模式,需权衡利弊