单例模式是C++设计模式中的基础且高频使用的模式,其核心价值在于控制类的实例化过程,保证全局范围内该类只有一个实例对象,同时提供统一的访问接口。这种模式在需要全局共享资源、控制唯一操作的场景中非常实用,比如日志管理器、配置管理器、数据库连接池等场景都会用到单例模式。

单例模式的基础实现方式
1. 饿汉式单例
饿汉式单例的核心思路是在程序启动阶段就完成实例的创建,依赖C++全局变量的初始化特性,天然具备线程安全性,因为全局变量的初始化在main函数执行前就已经完成。
#include <iostream>
class HungrySingleton {
private:
// 私有化构造函数,禁止外部直接实例化
HungrySingleton() {
std::cout << "饿汉式单例实例创建" << std::endl;
}
// 禁止拷贝构造函数和赋值运算符
HungrySingleton(const HungrySingleton&) = delete;
HungrySingleton& operator=(const HungrySingleton&) = delete;
public:
// 提供全局访问点
static HungrySingleton* getInstance() {
return instance;
}
// 示例方法
void doSomething() {
std::cout << "饿汉式单例执行操作" << std::endl;
}
private:
// 静态实例,程序启动时初始化
static HungrySingleton* instance;
};
// 类外初始化静态成员
HungrySingleton* HungrySingleton::instance = new HungrySingleton();
int main() {
HungrySingleton* singleton = HungrySingleton::getInstance();
singleton->doSomething();
return 0;
}
饿汉式的优点是实现简单、线程安全,缺点是如果单例实例占用资源较多,且程序全程没有使用该实例,会造成资源浪费。
2. 基础懒汉式单例
懒汉式单例采用延迟加载的思路,只有在第一次调用getInstance方法时才创建实例,能够避免不必要的资源占用,但基础版本存在线程安全问题。
#include <iostream>
class LazySingleton {
private:
LazySingleton() {
std::cout << "懒汉式单例实例创建" << std::endl;
}
LazySingleton(const LazySingleton&) = delete;
LazySingleton& operator=(const LazySingleton&) = delete;
public:
static LazySingleton* getInstance() {
// 第一次调用时才创建实例
if (instance == nullptr) {
instance = new LazySingleton();
}
return instance;
}
void doSomething() {
std::cout << "懒汉式单例执行操作" << std::endl;
}
private:
static LazySingleton* instance;
};
LazySingleton* LazySingleton::instance = nullptr;
int main() {
LazySingleton* singleton = LazySingleton::getInstance();
singleton->doSomething();
return 0;
}
在单线程环境下这个实现没有问题,但多线程场景下,多个线程可能同时判断instance == nullptr,导致创建多个实例,违反单例模式的核心要求。
进阶优化:线程安全的单例实现
1. 加锁实现的线程安全懒汉式
针对基础懒汉式的线程安全问题,最直接的优化方式是在实例创建前加互斥锁,保证同一时间只有一个线程能执行实例创建逻辑。
#include <iostream>
#include <mutex>
class ThreadSafeLazySingleton {
private:
ThreadSafeLazySingleton() {
std::cout << "线程安全懒汉式单例实例创建" << std::endl;
}
ThreadSafeLazySingleton(const ThreadSafeLazySingleton&) = delete;
ThreadSafeLazySingleton& operator=(const ThreadSafeLazySingleton&) = delete;
public:
static ThreadSafeLazySingleton* getInstance() {
// 加锁保证线程安全
m_mutex.lock();
if (instance == nullptr) {
instance = new ThreadSafeLazySingleton();
}
m_mutex.unlock();
return instance;
}
void doSomething() {
std::cout << "线程安全懒汉式单例执行操作" << std::endl;
}
private:
static ThreadSafeLazySingleton* instance;
static std::mutex m_mutex;
};
ThreadSafeLazySingleton* ThreadSafeLazySingleton::instance = nullptr;
std::mutex ThreadSafeLazySingleton::m_mutex;
int main() {
ThreadSafeLazySingleton* singleton = ThreadSafeLazySingleton::getInstance();
singleton->doSomething();
return 0;
}
这种方式虽然解决了线程安全问题,但每次调用getInstance都需要加锁解锁,会带来额外的性能开销,因为实例一旦创建完成,后续就不需要再加锁了。
2. 双重检查锁定(DCLP)优化
双重检查锁定先判断实例是否已经存在,不存在再加锁,加锁后再判断一次实例是否存在,这样既能保证线程安全,又能减少锁的性能开销。
#include <iostream>
#include <mutex>
#include <atomic>
class DCLPSingleton {
private:
DCLPSingleton() {
std::cout << "双重检查锁定单例实例创建" << std::endl;
}
DCLPSingleton(const DCLPSingleton&) = delete;
DCLPSingleton& operator=(const DCLPSingleton&) = delete;
public:
static DCLPSingleton* getInstance() {
// 第一次检查,实例存在直接返回
if (instance.load(std::memory_order_acquire) == nullptr) {
std::lock_guard<std::mutex> lock(m_mutex);
// 第二次检查,避免多线程同时进入第一次检查后重复创建
if (instance.load(std::memory_order_relaxed) == nullptr) {
instance.store(new DCLPSingleton(), std::memory_order_release);
}
}
return instance.load(std::memory_order_acquire);
}
void doSomething() {
std::cout << "双重检查锁定单例执行操作" << std::endl;
}
private:
static std::atomic<DCLPSingleton*> instance;
static std::mutex m_mutex;
};
std::atomic<DCLPSingleton*> DCLPSingleton::instance(nullptr);
std::mutex DCLPSingleton::m_mutex;
int main() {
DCLPSingleton* singleton = DCLPSingleton::getInstance();
singleton->doSomething();
return 0;
}
这里使用std::atomic配合内存序来保证指令不会重排,避免早期C++中双重检查锁定因为指令重排导致的未定义行为,是符合现代C++标准的实现方式。
3. 局部静态变量实现(C++11及以上推荐)
C++11标准规定,局部静态变量的初始化是线程安全的,编译器会保证局部静态变量只被初始化一次,因此可以用这种方式实现非常简洁且安全的单例。
#include <iostream>
class LocalStaticSingleton {
private:
LocalStaticSingleton() {
std::cout << "局部静态变量单例实例创建" << std::endl;
}
LocalStaticSingleton(const LocalStaticSingleton&) = delete;
LocalStaticSingleton& operator=(const LocalStaticSingleton&) = delete;
public:
static LocalStaticSingleton& getInstance() {
// 局部静态变量,C++11保证线程安全且仅初始化一次
static LocalStaticSingleton instance;
return instance;
}
void doSomething() {
std::cout << "局部静态变量单例执行操作" << std::endl;
}
};
int main() {
LocalStaticSingleton& singleton = LocalStaticSingleton::getInstance();
singleton.doSomething();
return 0;
}
这种实现方式不需要手动管理指针和锁,代码简洁,同时具备延迟加载、线程安全、自动释放实例的优点,是C++11及以上版本中最推荐的单例实现方式。
不同实现方式的对比
| 实现方式 | 线程安全 | 延迟加载 | 实现复杂度 | 适用场景 |
|---|---|---|---|---|
| 饿汉式单例 | 是 | 否 | 低 | 实例占用资源少,程序启动即需要使用的场景 |
| 基础懒汉式 | 否 | 是 | 低 | 仅单线程环境 |
| 加锁懒汉式 | 是 | 是 | 中 | 低版本C++,对性能要求不高的多线程场景 |
| 双重检查锁定 | 是 | 是 | 高 | 高版本C++,对性能要求高的多线程场景 |
| 局部静态变量 | 是 | 是 | 极低 | C++11及以上版本的所有多线程场景 |
单例模式的注意事项
实现单例模式时除了保证实例唯一和线程安全,还需要注意几个问题:首先是禁止拷贝和赋值,避免通过拷贝构造创建新的实例;其次是如果单例实例持有资源,需要合理处理资源释放,比如局部静态变量会在程序结束时自动析构,无需手动释放;最后是不要过度使用单例模式,单例本质上是一种全局状态,过度使用会增加代码的耦合度,不利于单元测试和代码维护。