设计模式~单例模式
单例模式
- 单例即该类只能实例化一个对象。
- 单例模式的构造函数私有化,拷贝构造,赋值运算符重载函数可直接delete。
- 需要一个静态函数接口,返回唯一静态实例对象。
单例模式的分类
- 饿汉式单例模式:唯一的实例对象是在main函数开始前就已经在数据区了。
- 懒汉式单例模式:唯一的实例对象在第一次调用获取实例对象时才构造对象。
饿汉式单例模式
class Singleton
{
public:
static Singleton* GetInstance()
{
return &instance;
}
private:
static Singleton instance;
Singleton() // 构造函数私有化
{
cout << "construct Sigleton" << endl;
}
Singleton3(const Singleton&) = delete;
Singleton& operator = (const Singleton&) = delete;
};
Singleton3 Singleton::instance; // 静态成员类外实例化
int main()
{
Singleton3* p1 = Singleton::GetInstance();
Singleton* p2 = Singleton::GetInstance();
cout << p1 << endl;
cout << p2 << endl;
return 0;
}
/*
结果:
construct Sigleton
00007FF76A3CE160
00007FF76A3CE160
可以看到构造函数只调用过一次,p1和p2的地址相同
*/
- 饿汉式单例模式一定是线程安全的,因为在程序启动时就已经生成唯一的对象了
需要加锁的懒汉式单例模式
#include <iostream>
#include <mutex>
using namespace std;
mutex mtx; // 全局的锁
/*
懒汉式单例模式: 本身不是线程安全的、因此需要进行控制、加锁操作
因为在调用第一次GetInstance时、在if条件中基本步骤是
1、开辟内存、2、构造对象、3、给instance赋值
这三部操作不是原子操作、因此会存在线程不安全
*/
class Singleton
{
public:
static Singleton* GetInstance()
{
// lock_guard<mutex> guard(mtx); 在此处加锁使得锁的粒度太大了
// 因为每一调用GetInstance都会加锁、解锁操作、即使是单线程也是
// 而应该是 锁+双重判断、将锁放在里边
if (instance == nullptr)
{
lock_guard<mutex> guard(mtx); // C++11标准,lock_guard是对加锁和加锁进行面向对象的封装
if (instance == nullptr)
{
instance = new Singleton();
}
}
return instance;
}
private:
/*
volatile 使用volatile指每次从内存中读取数据,
而不是从编译器优化后的缓存中读取数据,简单来讲就是防止编译器优化。
确保每次获取单例对象、修改单例对象、对所有调用者都是可见的
*/
static Singleton * volatile instance;
Singleton()
{
cout << "construct Sigleton" << endl;
}
Singleton(const Singleton&) = delete;
Singleton& operator = (const Singleton&) = delete;
};
// 类外初始化静态成员
Singleton * volatile Singleton::instance = nullptr;
int main()
{
Singleton* p1 = Singleton::GetInstance();
Singleton* p2 = Singleton::GetInstance();
cout << p1 << endl;
cout << p2 << endl;
return 0;
}
/*
结果:
construct Sigleton
000002669A186340
000002669A186340
*/
- 因为这种懒汉式单例模式本身是线程不安全的,因为GetInsatnce函数可能会同时有多个线程进入,且创建对象不是原子操作,因此需要加锁操作
线程安全的懒汉式单例模式
// 线程安全的懒汉单例模式
class Singleton
{
public:
static Singleton* GetInstance()
{
// 函数的局部静态对象在第一次调用此函数运行到这一句时才会实例化
// 且在反汇编上、已经对这一句自动加上线程互斥锁、确保对象构造过程中是原子操作的
static Singleton instance;
return &instance;
}
private:
Singleton()
{
cout << "construct Sigleton" << endl;
}
Singleton(const Singleton&) = delete;
Singleton& operator = (const Singleton&) = delete;
};
int main()
{
Singleton* p1 = Singleton::GetInstance();
Singleton* p2 = Singleton::GetInstance();
cout << p1 << endl;
cout << p2 << endl;
return 0;
}
/*
结果:
construct Sigleton
00007FF62274F170
00007FF62274F170
*/
- 调用获取实例对象的函数,在运行到static Singleton instance;时才会实例对象,并且在反汇编上已经加上了线程互斥锁,确保对象构造是原子的。
总结
- 单例模式适用于只能有一个实例对象的类,例如内存池等。
- 两种不同形式的单例模式,可以根据不同场景使用不同的模式。
新手上路,如有错误,请指出!!!