一、介绍
1.动机
在软件系统中,经常有这样一些特殊的类,必须保证它们在系统中只存在一个实例,才能确保它们的逻辑正确性、以及良好的效率。
如何绕过常规的构造器,提供一种机制来保证一个类只有一个实例?
这应该是类设计者的责任,而不是使用者的责任。
2.定义
保证一个类仅有一个实例,并提供一个该实例的全局访问点。------GOF
3.结构图
4.要点总结
- Singleton模式中的实例构造器可以设置为protected以允许子类派生。
- Singleton模式一般不支持拷贝构造函数和Clone接口,因为这有可能导致多个对象实例,与Singleton模式的初衷违背。
- 如何实现多线程环境下安全的Singleton?注意对双检查锁的正确实现。
二、单例模式
1.概念
单例模式的核心在于类自身负责创建自己的唯一实例,并提供一个静态方法来获取这个实例,从而防止外部代码创建多个实例。
①单例模式的优点:
- 节省资源,避免频繁创建和销毁对象。
- 方便控制资源的使用。
- 维护数据的一致性。
②单例模式的缺点:
- 在多线程环境下,需要考虑线程安全问题。
- 若使用锁机制可能会影响性能。
2.实现要点
单例模式的实现要点:
- 私有化构造函数:防止在外部通过构造函数直接创建对象。
- 禁用拷贝构造和赋值运算符:防止通过拷贝构造和赋值操作创建多个对象。
- 静态变量:存储类的唯一实例。
- 公有静态方法:提供一个全局访问点来获取这个实例。
单例模式分为饿汉式和懒汉式。
3.饿汉式
在程序启动时立即创建实例,因此本身是线程安全的。但无论是否使用实例,都会立即创建,可能导致资源浪费。
饿汉式单例:
cpp
class Singleton {
private:
static Singleton* pSingleton;
Singleton() {
cout << "Singleton()" << endl;
}
~Singleton() {
cout << "~Singleton()" << endl;
}
public:
Singleton(const Singleton&) = delete; //禁用拷贝构造函数
Singleton& operator=(const Singleton&) = delete; //禁用赋值运算符
static Singleton* getInstance() {
return pSingleton;
}
static void deleteInstance() { //用于删除实例
cout << "deleteInstance()" << endl;
if (pSingleton) {
delete pSingleton;
pSingleton = nullptr;
}
}
};
Singleton* Singleton::pSingleton = new Singleton();
//直接创建实例
测试:
cpp
Singleton* s1 = Singleton::getInstance();
Singleton* s2 = Singleton::getInstance();
cout << s1 << endl;
cout << s2 << endl;
Singleton::deleteInstance();
4.懒汉式
程序启动时实例并不存在,只有在需要使用时才会创建实例,这种方式要考虑线程安全的问题。
①使用静态局部变量实现懒汉式单例
cpp
class Singleton {
private:
Singleton() {
cout << "Singleton()" << endl;
}
~Singleton() {
cout << "~Singleton()" << endl;
}
public:
static Singleton* getInstance() {
static Singleton instance; //静态局部变量
return &instance;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
静态局部变量存储在静态存储区,只在当前函数内有效,其它函数无法访问。
静态局部变量只在第一次调用时初始化,生命周期从第一次初始化开始,到程序结束为止。
②使用双检查锁实现懒汉式单例
cpp
class Singleton {
private:
static mutex mtx; //互斥锁
static atomic<shared_ptr<Singleton>> pSingleton; //原子智能指针
Singleton() {
cout << "Singleton()" << endl;
}
public:
~Singleton() { //设置为公有,智能指针要调用
cout << "~Singleton()" << endl;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static shared_ptr<Singleton> getInstance() {
shared_ptr<Singleton> ptr = pSingleton.load(); //读取
if (!ptr) { //第一次检查
unique_lock<mutex> amtx(mtx);
ptr = pSingleton.load(); //读取
if (!ptr) { //第二次检查
ptr = shared_ptr<Singleton>(new Singleton);
pSingleton.store(ptr); //存储
}
}
return ptr;
}
};
mutex Singleton::mtx;
atomic<shared_ptr<Singleton>> Singleton::pSingleton = nullptr;
atomic的load和store成员函数用于以原子方式读取和存储原子变量。它们可以接受一个memory_order参数,该参数用于指定在内存模型中操作的内存顺序。如果不提供则会默认使用memory_order_seq_cst,这是最严格的内存顺序,它保证了读取操作的顺序性和内存可见性。
③使用call_once实现懒汉式单例
cpp
class Singleton {
private:
static once_flag flag; //用于标记
static shared_ptr<Singleton> pSingleton; //智能指针
Singleton() {
cout << "Singleton()" << endl;
}
public:
~Singleton() { //设置为公有,智能指针要调用
cout << "~Singleton()" << endl;
}
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
static shared_ptr<Singleton> getInstance() {
call_once(flag, [] { //最多调用一次
pSingleton = shared_ptr<Singleton>(new Singleton);
});
return pSingleton;
}
};
once_flag Singleton::flag;
shared_ptr<Singleton> Singleton::pSingleton = nullptr;
call_once可以让函数或代码块在多线程环境中最多只被执行一次。