饿汉式单例在程序启动时即初始化,线程安全但可能浪费资源;懒汉式延迟初始化节省资源,但需用双重检查锁定或std::call_once保障线程安全,且析构和内存泄漏风险更高。
饿汉式单例:启动时就构造,线程安全但可能浪费资源
饿汉式在程序加载时就完成实例初始化,天然线程安全,不用加锁。但它的问题是:哪怕你整个程序一次都没调用 getInstance(),构造函数也会执行,如果构造开销大(比如读配置、连数据库),就白耗资源。
实操建议:
- 把静态成员变量直接定义在类外,或用静态局部变量(C++11 起保证首次调用时初始化且线程安全)
- 构造函数设为
private,禁止外部 new;拷贝/移动构造和赋值全删掉(= delete) - 返回引用比返回指针更常见,避免裸指针生命周期困惑
示例关键片段:
class Singleton {
private:
Singleton() { /* heavy init */ }
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
static Singleton& getInstance() {
static Singleton instance; // C++11 线程安全的局部静态
return instance;
}
};
懒汉式单例:第一次调用才创建,需手动处理线程安全
懒汉式延迟初始化,节省资源,但多线程环境下极易出错——两个线程同时判断 instance == nullptr 为真,接着都执行 new,结果构造两次,返回不同对象。
立即学习“C++免费学习笔记(深入)”;
常见错误现象:
- 没加锁,多线程下出现多个实例(
getInstance()返回地址不一致) - 只对 new 加锁,但没对判空加锁,仍可能重复构造
- 用
std::call_once+std::once_flag是更简洁安全的选择(C++11)
推荐写法(双重检查锁定 DCLP,注意必须用 volatile 或原子指针防重排序,C++11 后推荐用 std::atomic):