# C++ 单例模式
# 简介
单例模式是指在当前程序当中,有且仅存在一个该实例对象,那么就得满足以下几个条件:
- 构造函数私有化,保证用户无法创建新的对象;
- 在私有变量当中定义该类唯一的实例对象;
- 保留一个 static 类型函数接口访问该对象
例如:
class T{ | |
public: | |
static T* GetQue(); | |
private: | |
T(); | |
vector<int> que; | |
}; |
# 单例模式又分为饿汉模式和懒汉模式
# 饿汉模式:
饿汉模式,顾名思义就是比较饥饿,在程序编译时候便已经初始化了;
优点:可以保证初始化时候的线程安全问题
- 因为当还在编译阶段,不存在资源竞争关系,此时初始化的实例是唯一的
- 一开始初始化可以测试程序性能瓶颈,以免在后续的运行当中初始化导致性能开销过大程序崩溃
缺点:不管是否需要用到该实例他都会存在内存当中,增加内存开销
# 饿汉模式代码实现:
#include <vector> | |
#include <iostream> | |
using namespace std; | |
class T{ | |
public: | |
static T* GetQue(); | |
void display(); | |
void res(); | |
private: | |
T(); | |
static T t; | |
vector<int> que; | |
}; | |
T T::t; | |
T* T::GetQue() { | |
return &t; | |
} | |
T::T() { | |
for(int i=0;i<10;i++){ | |
que.emplace_back(i); | |
} | |
} | |
void T::display() { | |
for(auto index:que){ | |
cout<<index<<" "; | |
} | |
cout<<endl; | |
} | |
void T::res() { | |
que[0]=100; | |
que[1]=101; | |
} |
#include "test/test.h" | |
void test(){ | |
T *q=T::GetQue(); | |
q->display(); | |
} | |
int main() | |
{ | |
T *q=T::GetQue(); | |
q->display(); | |
q->res(); | |
test(); | |
return 0; | |
} |
运行结果:
test()
调用 res()
函数改变了值,在 test()
函数当中读取依然生效
# 懒汉模式:
该模式为,当你需要用到的时候才会进行初始化,在程序初始化时候只是把对象初始化为了 nullptr
,当使用到时候才进行实例化
** 优点:** 支持延迟加载,用到时候才会进行初始化,节省内存开销
** 缺点:** 容易产生性能安全问题,即多个线程调用初始化函数时候
- A 线程调用初始化函数判断 t 对象为空准备进行初始化,B 线程调用初始化函数判断 t 对象为空也准备初始化,然后 A 线程初始化完成,B 线程再次对该对象进行初始化
# 懒汉模式单线程代码实现:
#include <vector> | |
#include <iostream> | |
using namespace std; | |
class T{ | |
public: | |
static T* GetQue(); | |
void display(); | |
void res(); | |
private: | |
T(); | |
static T *t; | |
vector<int> que; | |
}; | |
T* T::t= nullptr; | |
T* T::GetQue() { | |
if(t==nullptr){ | |
t=new T(); | |
} | |
return t; | |
} | |
T::T() { | |
for(int i=0;i<10;i++){ | |
que.emplace_back(i); | |
} | |
} | |
void T::display() { | |
for(auto index:que){ | |
cout<<index<<" "; | |
} | |
cout<<endl; | |
} | |
void T::res() { | |
que[0]=100; | |
que[1]=101; | |
} | |
// 主函数代码与之前一致 |
# 懒汉模式多线程实现代码(一)
因为懒汉模式会导致多次实例化对象,所以我们可以才对对象进行实例化的时候加一把锁即可避免只有一个线程对类进行实例化
T* T::GetQue() { | |
std::lock_guard<std::mutex> lock(mutex_lock); | |
if(t==nullptr){ | |
t=new T(); | |
} | |
return t; | |
} |
但是这样就会导致每次线程调用获取对象的时候都需要加锁,再释放锁,实际上只有第一次初始化时候需要加锁,这样子无疑会拖慢程序运行速度,所以我们可以使用一个双重检测机制来缩小锁的范围
T* T::GetQue() { | |
if(t==nullptr){ | |
std::lock_guard<std::mutex> lock(mutex_lock); | |
if(t==nullptr){ | |
t=new T(); | |
} | |
} | |
return t; | |
} |
# 懒汉模式多线程实现代码(二)
采用静态局部变量的初始化方式即可避免多线程安全问题
class T{ | |
public: | |
static T* GetQue(); | |
void display(); | |
void res(); | |
private: | |
T(); | |
vector<int> que; | |
}; | |
T* T::GetQue() { | |
static T t; | |
return &t; | |
} |
以上这个代码我们可以在 GetQue()
函数这里打上断点,分析该段代码的汇编即可得到答案
对于静态局部变量的初始化编译器会自动加上锁,释放锁保证他的线程安全问题