先看代码
static 全局/静态实例:
cpp
// 方式一:全局静态对象
class Config {
public:
void load() { /* ... */ }
std::string get(const std::string& key) { return data[key]; }
private:
std::map<std::string, std::string> data;
};
static Config g_config; // 全局静态实例,整个程序共享一个
单例模式:
cpp
// 方式二:单例模式
class Config {
public:
static Config& getInstance() {
static Config instance; // C++11 保证线程安全
return instance;
}
void load() { /* ... */ }
std::string get(const std::string& key) { return data[key]; }
private:
Config() {} // 构造函数私有
Config(const Config&) = delete; // 禁止拷贝
Config& operator=(const Config&) = delete;
std::map<std::string, std::string> data;
};
// 使用
Config::getInstance().load();
核心区别对比
| 维度 | static 实例 | 单例模式 |
|---|---|---|
| 唯一性保证 | ❌ 无法阻止别人再 new 一个或拷贝 |
✅ 从语言层面强制只有一个实例 |
| 构造时机 | 程序启动时即构造(可能过早) | 第一次调用时才构造(懒加载) |
| 拷贝/多实例风险 | 有,外部可以 Config c2 = g_config; |
无,拷贝构造被 delete |
| 访问方式 | 直接用变量名,有命名冲突风险 | 通过统一接口 getInstance() |
| 可测试性 | 难以替换/mock | 可以扩展为支持替换实现 |
| 初始化顺序问题 | 多个全局静态对象间顺序不确定(static initialization order fiasco) | 懒加载规避了大部分此类问题 |
最关键的两点展开说
1. 唯一性:static 实例根本挡不住多实例
cpp
static Config g_config; // 这里有一个
Config another; // 没人阻止这行代码!
Config* p = new Config(); // 也没人阻止这行!
Config copy = g_config; // 拷贝了一份,现在有两个了
单例从构造函数私有化就从根上杜绝了这种情况。
2. 初始化顺序陷阱(Static Initialization Order Fiasco)
cpp
// file_a.cpp
static Config g_config; // 什么时候初始化?不确定
// file_b.cpp
static Logger g_logger; // 如果 Logger 构造时依赖 g_config,可能崩溃
// 单例就没这个问题:
// Logger 构造时调用 Config::getInstance(),此时 Config 一定会被初始化
什么时候用哪个?
- 如果只是需要一个"方便访问的共享对象",且不在意是否有人创建多份 → static 实例足够
- 如果需要强制保证全局唯一、控制构造时机、防止误用 → 用单例模式
单例模式本质上是用设计手段把"约定"变成了"强制",这是两者最根本的差异。