单例模式(Singleton)------ 全局就我一个,多了没有,爱用不用
大白话解释
想象一个公司只有一台打印机。不管谁来打印,用的都是同一台机器。如果每次有人要打印就新买一台,那公司得破产。
单例模式就是:整个程序运行期间,某个类只能有一个实例,所有人都共用这一个。
常见场景:
- 配置文件管理器(配置只需加载一次)
- 日志系统(全局写同一个日志)
- 数据库连接池(连接资源宝贵)
- 窗口管理器(只有一个主窗口)
核心思路
- 把构造函数藏起来 (设为 private),外面没法
new出来 - 自己内部保存唯一实例
- 提供一个全局访问点(静态方法)
C++ 代码示例
版本一:懒汉式(用到的时候才创建)
cpp
#include <iostream>
#include <string>
class ConfigManager {
private:
// 私有构造函数,外部无法直接 new
ConfigManager() {
std::cout << "配置管理器初始化完成!\n";
dbHost = "localhost";
dbPort = 3306;
}
// 禁止拷贝和赋值(C++11 写法)
ConfigManager(const ConfigManager &) = delete;
ConfigManager &operator=(const ConfigManager &) = delete;
std::string dbHost;
int dbPort;
public:
// 全局访问点:第一次调用时创建实例
static ConfigManager &instance() {
static ConfigManager instance; // C++11 保证线程安全
return instance;
}
std::string getDbHost() const { return dbHost; }
int getDbPort() const { return dbPort; }
void setDbHost(const std::string& host) { dbHost = host; }
};
int main() {
// 获取单例
ConfigManager &config1 = ConfigManager::instance();
ConfigManager &config2 = ConfigManager::instance();
// 验证是同一个对象
std::cout << "是同一个对象吗?" << (&config1 == &config2 ? "是" : "否") << "\n";
// 通过 config1 修改
config1.setDbHost("192.168.0.100");
// config2 也能看到修改(因为本来就是同一个)
std::cout << "config2 的数据库地址:" << config2.getDbHost() << "\n";
return 0;
}
输出:
配置管理器初始化完成!
是同一个对象吗?是
config2 的数据库地址:192.168.0.100
版本二:线程安全的指针版(多线程环境)
cpp
#include <iostream>
#include <mutex>
class Logger {
private:
static Logger *instance;
static std::mutex mtx;
Logger() {
std::cout << "日志系统启动\n";
}
public:
static Logger *instance() {
if (instance == nullptr) { // 第一次检查(提高效率)
std::lock_guard<std::mutex> lock(mtx);
if (instance == nullptr) { // 第二次检查(保证线程安全)
instance = new Logger();
}
}
return instance;
}
void log(const std::string &msg) {
std::cout << "[LOG] " << msg << "\n";
}
};
// 静态成员初始化
Logger *Logger::instance = nullptr;
std::mutex Logger::mtx;
int main() {
Logger::instance()->log("程序启动");
Logger::instance()->log("用户登录成功");
return 0;
}
优缺点
| 说明 | |
|---|---|
| ✅ 优点 | 节约资源,避免重复创建 |
| ✅ 优点 | 全局共享资源,防止冲突 |
| ✅ 优点 | 控制访问,全局只有一个入口 |
| ❌ 缺点 | 没有接口,没有继承,很难扩展新功能 |
| ❌ 缺点 | 单元测试困难(全局状态难以隔离) |
| ❌ 缺点 | 生命周期管理不灵活,一旦创建,无法手动销毁、重启 |
| ❌ 缺点 | 到处都能直接调用,代码可读性变差,难以清理依赖 |