设计模式代表了最佳实践,是软件开发过程中面临一般问题的解决方案。
设计模式是一套被反复使用、经过分类、代码设计总结的经验。
二、单例模式
单例模式也叫单件模式。Singleton是一个非常常用的设计模式,几乎所有稍微大一些的程序都会使用到它,所以构建一个线程安全并且高效的Singleton很重要。
1. 单例类保证全局只有一个唯一实例对象。
2. 单例类提供获取这个唯一实例的接口。
以下先介绍下面试问的比较多的两个题:
1.只能在栈上生成的对象的类
2。只能在堆上生成对象的类
//只能在栈上生成对象的类
class Singleton
{
public:
<span style="white-space:pre"> </span>statci Singleton* GetSingletong()
<span style="white-space:pre"> </span>{
<span style="white-space:pre"> </span>if(_sInstance == NULL)
<span style="white-space:pre"> </span>_sInstance = new Singleton;
<span style="white-space:pre"> </span>return _sInstance;
<span style="white-space:pre"> </span>}
private:
<span style="white-space:pre"> </span>int _a;
<span style="white-space:pre"> </span>static Singleton* _sInstance;
<span style="white-space:pre"> </span>Singletong()
<span style="white-space:pre"> </span>:_a(5)
<span style="white-space:pre"> </span>{}
<span style="white-space:pre"> </span>Singletong(const Singleton&);
<span style="white-space:pre"> </span>Singletong& operator=(const Singleton&);
};
</pre><pre name="code" class="html">//只能在栈上生成对象的单例模式
class Singleton
{
public:
<span style="white-space:pre"> </span>static Singletong* GetSingletong()
<span style="white-space:pre"> </span>{
<span style="white-space:pre"> </span>static Singletong s;
<span style="white-space:pre"> </span>return &s;
<span style="white-space:pre"> </span>}
private:
<span style="white-space:pre"> </span>int _a;
<span style="white-space:pre"> </span>Singletong()
<span style="white-space:pre"> </span>:_a(5)
<span style="white-space:pre"> </span>{}
<span style="white-space:pre"> </span>Singleton(const Singletong&);
<span style="white-space:pre"> </span>Singletong& operator=(const Singletong&);
}
第一、为了实现线程安全,加锁!!!
第二、因为每次获取单例对象都需要做加锁解锁操作,而实际上只有第一次产生单例模式需要这么做,所以使用双重判断来提高效率。
第三、为了防止系统优化进行指令重排,使用栅栏,防止得到的对象为随机值。
就具体实现如下:
//高效、线程安全的单例模式
#include <mutex>
#include <Windows.h>
<pre name="code" class="cpp" style="font-size: 16px;">class Singleton
{
public:
<span> </span>statci Singleton* GetSingletong()
<span> </span>{
<span style="white-space:pre"> </span>if(_sInstance == NULL)//双重判断提高效率
<span style="white-space:pre"> </span>{
<span style="white-space:pre"> </span>lock_guard<mutex> lck(_mtx);//加锁线程安全
<span> </span>if(_sInstance == NULL)
<span style="white-space:pre"> </span>{
<span style="white-space:pre"> </span>Singletong* tmp = new Singletong;
<span style="white-space:pre"> </span>memoryBarrier();//使用栅栏防止编译器优化使,使指令重排
</pre><pre name="code" class="cpp" style="font-size: 16px;"><span style="white-space:pre"> <span style="font-size: 12pt; color: rgb(0, 176, 240);">//<span style="font-size: 12pt;">1.分配空间 2.调用构造函数 3.赋值
<span style="font-size: 12pt;"><span style="white-space:pre"> </span>//<span style="font-size: 12pt;">编译器编译优化可能会把2<span style="font-size: 12pt;">和3进行指令重排,这样可能会导致
<span style="font-size: 12pt;"><span style="white-space:pre"> </span>//<span style="font-size: 12pt;">高并发场<span style="font-size: 12pt;">景下<span style="font-size: 12pt;">,其他线程获取到未调用构造函数初始化的对象
<span style="font-size: 12pt;"><span style="white-space:pre"> </span>//<span style="font-size: 12pt;">以下加入<span style="font-size: 12pt;">内存栅栏进行处理,防止编译器重排栅栏后面的赋值
<span style="font-size: 12pt;"><span style="white-space:pre"> </span>//<span style="font-size: 12pt;">到内存栅栏之前</span></span></span></span></span></span></span></span></span></span></span></span></span><br style="text-align: -webkit-auto;" /></span>
</span><pre name="code" class="cpp" style="font-size: 16px;"><span style="white-space:pre"> </span>_sInstance = tmp;
<span style="white-space:pre"> </span>}
<span> </span>
<span style="white-space:pre"> </span>}
<span> </span>return _sInstance;
<span> </span>}
private:
<span> </span>int _a;
<span> </span>static Singleton* _sInstance;
<span style="white-space:pre"> </span>static mutex _mtx;//互斥锁<span style="white-space:pre"> </span>
<span> </span>Singletong()
<span> </span>:_a(5)
<span> </span>{}
</pre><pre name="code" class="cpp" style="font-size: 16px;"><span> </span>Singletong(const Singleton&);
<span> </span>Singletong& operator=(const Singleton&);
};
上面实现的模式叫做懒汉模式,下面讲解下饿汉模式的单例模式
线程安全(饿汉模式--简洁、高效、不用加锁、但是在某些场景下会有缺陷)
//方式一
class Singleton
{public:
//获取唯一对象实例的接口函数
static Singleton* GetInstance()
{
<span style="white-space:pre"> </span>static Singleton sInstance;
<span style="white-space:pre"> </span>return &sInstance;
}
void Print()
{
<span style="white-space:pre"> </span>cout<<_data<<endl;
}
private:
//构造函数定义为私有,限制只能在类内创建对象
Singleton()
:_data(0)
{}
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
//单例类里面的数据
int _data;
};
void TestSingleton()
{
<span style="white-space:pre"> </span>Singleton::GetInstance()->Print();
} //
方式二
class Singleton
{
public:
//获取唯一对象实例的接口函数
static Singleton* GetInstance()
{
<span style="white-space:pre"> </span>assert(_sInstance);
<span style="white-space:pre"> </span>return _sInstance;
}
//删除实例对象
static void DelInstance()
{
<span style="white-space:pre"> </span>if (_sInstance)
<span style="white-space:pre"> </span>{
<span style="white-space:pre"> </span>delete _sInstance;
<span style="white-space:pre"> </span>_sInstance = NULL;
<span style="white-space:pre"> </span>}
}
void Print()
{
<span style="white-space:pre"> </span>cout << _data << endl;
}
private:
//构造函数定义为私有,限制只能在类内创建对象
Singleton()
:_data(0)
{}
Singleton(const Singleton&);
Singleton& operator=(const Singleton&);
//指向实例的指针定义为静态私有,这样定义静态成员函数获取对象实例
static Singleton* _sInstance;
//单例类里面的数据
int _data;
};
Singleton* Singleton::_sInstance = new Singleton;
void TestSingleton()
{
<span style="white-space:pre"> </span>Singleton::GetInstance()->Print();
<span style="white-space:pre"> </span>Singleton::DelInstance();
}
带RAII GC 自动回收实例对象的方式
class Singleton
{
public:
//获取唯一对象实例的接口函数
static Singleton* GetInstance()
{
assert(_sInstance);
return _sInstance;
}
//删除实例对象
static void DelInstance()
{
if (_sInstance)
{
delete _sInstance;
_sInstance = NULL;
}
}
void Print()
{
cout << _data << endl;
}
class GC
{
public:
~GC()
{
cout << "DelInstance()"<<endl;
DelInstance();
}
};
private:
//构造函数定义为私有,限制只能在类内创建对象
Singleton()
:_data(0)
{}
//指向实例的指针定义为静态私有,这样定义静态成员函数获取对象实例
static Singleton* _sInstance;
//单例类里面的数据
int _data;
};
//静态对象在main函数之前初始化,这时只有主线程运行,所以是线程安全的。
Singleton* Singleton::_sInstance = new Singleton;
//使用RAII,定义全局的GC对象释放对象实例Singleton::GC gc;
void TestSingleton()
{
Singleton::GetInstance()->Print();
}