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(); }