单例模式是设计模式中的一种创建型模式,它的核心约束是让某个类在整个程序运行周期内只能存在一个实例对象,并且需要向所有调用方提供获取这个唯一实例的统一接口。在C++开发中,单例模式常被用于管理全局配置、日志系统、数据库连接池等只需要单一实例的场景。

基础单例实现(非线程安全)
最基础的单例实现方式是懒汉式,也就是在第一次调用获取实例的方法时才创建实例,这种方式可以延迟实例的创建时机,减少程序启动时的开销。但这种方式在多线程环境下会出现问题,多个线程同时调用获取实例的方法时,可能会创建出多个实例,违背单例的核心约束。
基础实现的代码如下:
#include <iostream>
class Singleton {
private:
// 构造函数私有化,禁止外部直接创建实例
Singleton() {
std::cout << "Singleton instance created" << std::endl;
}
// 拷贝构造函数和赋值运算符私有化,禁止实例拷贝
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
// 静态成员变量,存储唯一实例指针
static Singleton* instance;
public:
// 静态方法,获取唯一实例
static Singleton* getInstance() {
if (instance == nullptr) {
instance = new Singleton();
}
return instance;
}
// 示例方法
void doSomething() {
std::cout << "Singleton do something" << std::endl;
}
};
// 静态成员初始化
Singleton* Singleton::instance = nullptr;
int main() {
Singleton* s1 = Singleton::getInstance();
Singleton* s2 = Singleton::getInstance();
s1->doSomething();
// 验证两个指针是否指向同一个实例
if (s1 == s2) {
std::cout << "s1 and s2 are same instance" << std::endl;
}
return 0;
}
线程安全的懒汉式单例
要解决基础懒汉式的线程安全问题,需要在创建实例的时候加锁,保证同一时间只有一个线程能够执行实例创建的逻辑。C++11之后可以使用std::mutex来实现互斥锁,也可以使用C++11之后的内存栅栏特性配合双重检查锁定来优化性能,避免每次获取实例都需要加锁的开销。
双重检查锁定的实现代码如下:
#include <iostream>
#include <mutex>
class Singleton {
private:
Singleton() {
std::cout << "Thread safe Singleton instance created" << std::endl;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static Singleton* instance;
// 静态互斥锁,用于线程同步
static std::mutex mtx;
public:
static Singleton* getInstance() {
// 第一次检查,避免已经创建实例后还加锁
if (instance == nullptr) {
std::lock_guard<std::mutex> lock(mtx);
// 第二次检查,避免多个线程同时通过第一次检查后重复创建实例
if (instance == nullptr) {
instance = new Singleton();
}
}
return instance;
}
void doSomething() {
std::cout << "Thread safe Singleton do something" << std::endl;
}
};
Singleton* Singleton::instance = nullptr;
std::mutex Singleton::mtx;
int main() {
Singleton* s1 = Singleton::getInstance();
Singleton* s2 = Singleton::getInstance();
s1->doSomething();
if (s1 == s2) {
std::cout << "s1 and s2 are same instance" << std::endl;
}
return 0;
}
饿汉式单例实现
饿汉式单例的特点是在程序启动阶段就创建好实例,不需要考虑线程安全问题,因为静态成员的初始化在程序启动时就完成了,这时候还没有多线程环境。但饿汉式的缺点是如果单例的创建开销很大,会拖慢程序的启动速度,而且如果程序全程没有用到这个单例,也会造成资源浪费。
饿汉式的实现代码如下:
#include <iostream>
class Singleton {
private:
Singleton() {
std::cout << "Hungry Singleton instance created" << std::endl;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
// 直接初始化静态实例
static Singleton instance;
public:
static Singleton* getInstance() {
return &instance;
}
void doSomething() {
std::cout << "Hungry Singleton do something" << std::endl;
}
};
// 静态成员初始化,程序启动时就会创建实例
Singleton Singleton::instance;
int main() {
Singleton* s1 = Singleton::getInstance();
Singleton* s2 = Singleton::getInstance();
s1->doSomething();
if (s1 == s2) {
std::cout << "s1 and s2 are same instance" << std::endl;
}
return 0;
}
C++11之后的简洁线程安全实现
C++11标准规定,函数内的静态局部变量的初始化是线程安全的,利用这个特性可以非常简洁地实现线程安全的懒汉式单例,不需要手动加锁,代码可读性和维护性都更好,也是目前C++开发中推荐的单例实现方式。
实现代码如下:
#include <iostream>
class Singleton {
private:
Singleton() {
std::cout << "C++11 Singleton instance created" << std::endl;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
public:
static Singleton* getInstance() {
// 静态局部变量,初始化线程安全,且只初始化一次
static Singleton instance;
return &instance;
}
void doSomething() {
std::cout << "C++11 Singleton do something" << std::endl;
}
};
int main() {
Singleton* s1 = Singleton::getInstance();
Singleton* s2 = Singleton::getInstance();
s1->doSomething();
if (s1 == s2) {
std::cout << "s1 and s2 are same instance" << std::endl;
}
return 0;
}
单例模式的注意事项
在使用单例模式的时候需要注意几个问题。首先单例类的构造函数、拷贝构造函数、赋值运算符都需要私有化,避免外部创建实例或者拷贝实例。其次如果单例持有资源,需要考虑资源的释放时机,比如如果是动态分配的内存,可以在单例内部实现一个销毁方法,或者使用智能指针来管理实例的生命周期。另外单例模式虽然方便,但不要过度使用,因为单例的全局访问特性会增加代码的耦合度,不利于单元测试和代码维护。
不同实现方式对比
下面是几种常见单例实现方式的对比:
| 实现方式 | 线程安全 | 懒加载 | 实现复杂度 | 适用场景 |
|---|---|---|---|---|
| 基础懒汉式 | 否 | 是 | 低 | 单线程环境 |
| 双重检查锁定懒汉式 | 是 | 是 | 中 | 多线程环境,需要懒加载 |
| 饿汉式 | 是 | 否 | 低 | 多线程环境,实例创建开销小 |
| C++11静态局部变量式 | 是 | 是 | 低 | 多线程环境,推荐优先使用 |