单例模式是面向对象设计中常用的创建型模式,目的是确保某个类在全局范围内只有一个实例,并且提供统一的访问入口。在C++中,饿汉式和懒汉式是两种最主流的实现方式,二者的核心区别在于实例的初始化时机不同,适用的场景也有明显差异。

单例模式的核心约束
无论采用哪种实现方式,单例模式都需要满足三个基本条件:
- 构造函数私有化,禁止外部直接通过
new关键字创建实例 - 类内部维护一个自身的静态实例指针或引用
- 提供一个公开的静态方法,用于获取唯一的实例
饿汉式单例实现
饿汉式的核心特点是实例在程序启动时就已经完成初始化,不需要考虑多线程竞争的问题,实现逻辑相对简单。
基础实现代码
#include <iostream>
class HungrySingleton {
private:
// 私有构造函数,禁止外部实例化
HungrySingleton() {
std::cout << "饿汉式单例实例创建" << std::endl;
}
// 静态实例,程序启动时直接初始化
static HungrySingleton instance;
public:
// 禁止拷贝构造函数和赋值运算符
HungrySingleton(const HungrySingleton&) = delete;
HungrySingleton& operator=(const HungrySingleton&) = delete;
// 获取单例实例的静态方法
static HungrySingleton& getInstance() {
return instance;
}
// 示例方法
void doSomething() {
std::cout << "饿汉式单例执行操作" << std::endl;
}
};
// 类外初始化静态实例
HungrySingleton HungrySingleton::instance;
int main() {
// 获取单例实例并调用方法
HungrySingleton::getInstance().doSomething();
return 0;
}
饿汉式的优缺点
饿汉式的优势是实现简单,不需要处理线程同步问题,因为静态实例的初始化在程序进入main函数之前就已经完成,多线程环境下不会出现重复创建的问题。缺点是如果单例类的初始化成本较高,会提前占用系统资源,即使后续不会用到这个实例,资源也无法释放。
懒汉式单例实现
懒汉式的实例只有在第一次被调用时才会创建,能够避免不必要的资源占用,但需要考虑多线程环境下的线程安全问题。
线程不安全的懒汉式实现
#include <iostream>
class LazySingleton {
private:
// 私有构造函数
LazySingleton() {
std::cout << "懒汉式单例实例创建" << std::endl;
}
// 静态实例指针,初始化为nullptr
static LazySingleton* instance;
public:
// 禁止拷贝和赋值
LazySingleton(const LazySingleton&) = delete;
LazySingleton& operator=(const LazySingleton&) = delete;
// 获取单例实例的方法
static LazySingleton* getInstance() {
if (instance == nullptr) {
instance = new LazySingleton();
}
return instance;
}
void doSomething() {
std::cout << "懒汉式单例执行操作" << std::endl;
}
};
// 初始化静态指针
LazySingleton* LazySingleton::instance = nullptr;
int main() {
LazySingleton::getInstance()->doSomething();
return 0;
}
这种实现方式在单线程环境下可以正常工作,但多线程同时调用getInstance方法时,可能会出现多个线程同时判断instance为nullptr,从而创建多个实例,违反单例的原则。
线程安全的懒汉式实现(双重检查锁)
#include <iostream>
#include <mutex>
class SafeLazySingleton {
private:
SafeLazySingleton() {
std::cout << "线程安全懒汉式单例实例创建" << std::endl;
}
static SafeLazySingleton* instance;
static std::mutex mtx; // 互斥锁,用于线程同步
public:
SafeLazySingleton(const SafeLazySingleton&) = delete;
SafeLazySingleton& operator=(const SafeLazySingleton&) = delete;
static SafeLazySingleton* getInstance() {
// 第一次检查,避免已经初始化后还要加锁的性能损耗
if (instance == nullptr) {
std::lock_guard<std::mutex> lock(mtx);
// 第二次检查,防止多个线程同时通过第一次检查后重复创建
if (instance == nullptr) {
instance = new SafeLazySingleton();
}
}
return instance;
}
void doSomething() {
std::cout << "线程安全懒汉式单例执行操作" << std::endl;
}
};
SafeLazySingleton* SafeLazySingleton::instance = nullptr;
std::mutex SafeLazySingleton::mtx;
int main() {
SafeLazySingleton::getInstance()->doSomething();
return 0;
}
基于局部静态变量的懒汉式实现
C++11之后,局部静态变量的初始化是线程安全的,因此可以用更简洁的方式实现线程安全的懒汉式单例:
#include <iostream>
class LocalStaticLazySingleton {
private:
LocalStaticLazySingleton() {
std::cout << "局部静态变量懒汉式单例实例创建" << std::endl;
}
public:
LocalStaticLazySingleton(const LocalStaticLazySingleton&) = delete;
LocalStaticLazySingleton& operator=(const LocalStaticLazySingleton&) = delete;
static LocalStaticLazySingleton& getInstance() {
// 局部静态变量,第一次调用时初始化,C++11保证线程安全
static LocalStaticLazySingleton instance;
return instance;
}
void doSomething() {
std::cout << "局部静态变量懒汉式单例执行操作" << std::endl;
}
};
int main() {
LocalStaticLazySingleton::getInstance().doSomething();
return 0;
}
两种实现方式的对比
| 对比维度 | 饿汉式 | 懒汉式 |
|---|---|---|
| 初始化时机 | 程序启动时 | 首次调用getInstance时 |
| 线程安全 | 天然线程安全 | 需要额外处理才线程安全 |
| 资源占用 | 提前占用资源,可能浪费 | 按需分配,更节省资源 |
| 实现复杂度 | 简单 | 需要考虑线程同步,复杂度稍高 |
| 适用场景 | 实例初始化成本低、大概率会被使用 | 实例初始化成本高、可能不会被使用 |
实现注意事项
- 单例类的构造函数一定要私有化,否则外部可以直接创建实例,破坏单例约束
- 一定要禁用拷贝构造函数和赋值运算符,避免通过拷贝方式创建新的实例
- 如果单例需要管理资源,还需要考虑实例的销毁逻辑,避免内存泄漏
- C++11及以上版本优先使用局部静态变量的懒汉式实现,代码简洁且线程安全有保障