单例的核心,无论初始化多少次,获取到的是同一个对象。
首先想到的是,利用局部static对象特性,产生全局唯一的对象。但是如何来避免更多的对象被实例化出来呢。
我们知道在类的编译时,编译器都会默认生成以下四个函数:构造函数,拷贝构造函数,析构函数以及赋值运算符重载函数。这四个函数默认都是public型的,保证外部能够调用。通过这四个函数,外部可以实例化对象,拷贝对象,析构等等。那很容易想到,把这四个函数设置为private就可以避免类在外部被实例化了。
最后,如何保证线程的安全性呢。在一些编译器中static 局部变量并不是线程安全的?有两种方案,加锁和利用类内static对象在main函数之前初始化来保证线程安全。
其他一些基本知识点:
围绕上面,单例大概如下几种:饿汉模式、懒汉模式、局部static模式、加锁模式、结合模板的通用单例化模式等。下面依次来介绍:
饿汉模式是指,无论是否使用,程序启动后,就会把单例实例化出来。
//饿汉模式
class CSingleInstanceA
{
private:
CSingleInstanceA(){
std::cout<<"CSingleInstanceA construct."<<std::endl;
};
~CSingleInstanceA(){
std::cout<<"CSingleInstanceA desconstruct."<<std::endl;
};
CSingleInstanceA(const CSingleInstanceA& csa){
};
private:
static CSingleInstanceA m_instance ;//这里只是声明,怎么保证已经初始化了呢
public:
static CSingleInstanceA* GetInstance(){ //思考一下这里为什么不能是以对象的形式返回?如果返回对象,对象就在外面,就要在外面来销毁,而析构函数是私有的,所以不能有外面来销毁。如果这里改成返回对象类型,会报析构函数是private的错误
return &m_instance ;
};
void DoSomething()
{
std::cout<<"hello A. "<<std::endl;
}
};
CSingleInstanceA CSingleInstanceA::m_instance;//这里的初始一定要有,这块的初始化一般是放在cpp文件中,其初始化顺序在main函数之前。
懒汉模式,是指在使用到时,才去初始化,没有用到就不处理。有两种方法。指针版和局部static变量版。
下面先看一下指针版的懒汉模式。
class CSingleInstanceB
{
private:
CSingleInstanceB(){ std::cout<<"construct."<<std::endl;};
~CSingleInstanceB(){
std::cout<<"desconstruct."<<std::endl;
};
CSingleInstanceB(const CSingleInstanceB& csb){};
private:
static CSingleInstanceB* m_instance ;//static CSingleInstanceB* m_instance = NULL; //这种方式初始化也是不行的 ,因为 const static 的变量才可以。static属于全局的,必须在外部初始化。
public:
static CSingleInstanceB *GetInstance()
{
if(m_instance == NULL)
{
m_instance = new CSingleInstanceB();//先分配空间,在调用构造函数
}
return m_instance;
}
};
CSingleInstanceB* CSingleInstanceB::m_instance = NULL;//指针必须初始化。*/
这个有明显的问题,是new出来的对象没有被释放。借助一个static函数来专门delete 这块内存。改进版如下。
class CSingleInstanceB
{
private:
CSingleInstanceB(){ std::cout<<"construct."<<std::endl;};
~CSingleInstanceB(){
std::cout<<"desconstruct."<<std::endl;
};
CSingleInstanceB(const CSingleInstanceB& csb){};
private:
static CSingleInstanceB* m_instance ;//static CSingleInstanceB* m_instance = NULL; //这种方式初始化也是不行的 ,因为 const static 的变量才可以。static属于全局的,必须在外部初始化。
public:
static CSingleInstanceB *GetInstance()
{
if(m_instance == NULL)
{
m_instance = new CSingleInstanceB();//先分配空间,在调用构造函数
}
return m_instance;
}
static void Destroy() //改进版加入销毁static类,让外部可以来回收new的资源
{
delete CSingleInstanceB::m_instance;
}
};
CSingleInstanceB* CSingleInstanceB::m_instance = NULL;//指针必须初始化。
//使用方法
CSingleInstanceB::GetInstance();//获取单例
CSingleInstanceB::Destroy();//对单例进行资源释放。
这种方法,获取单例后都要加一句释放语句,写代码中很容易遗忘,且看起来不是那么优雅。怎么避免这个主动释放呢?类对象在销毁时会自动调用析构函数来进行资源回收操作,借用这个特性,加一个内部类来实现形成如下版本:
class CSingleInstanceB
{
public:
class InnerHelper
{
public:
InnerHelper(){
std::cout<<"InnerHelper construct."<<std::endl;
};
~InnerHelper()
{
if(CSingleInstanceB::m_instance != NULL)
{
std::cout<<"delete m_instance."<<std::endl;
delete CSingleInstanceB::m_instance;
}
}
};
private:
CSingleInstanceB(){
std::cout<<"construct."<<std::endl;
};
~CSingleInstanceB(){
std::cout<<"desconstruct."<<std::endl;
};
CSingleInstanceB(const CSingleInstanceB& csb){};
private:
static CSingleInstanceB* m_instance ;//static CSingleInstanceB* m_instance = NULL; //这种方式初始化也是不行的 ,因为 const static 的变量才可以。static属于全局的,必须在外部初始化。
public:
static InnerHelper ih;//注意这里只是初始化,static类型的必须在类外面进行显示初始化
static CSingleInstanceB *GetInstance()
{
if(CSingleInstanceB::m_instance == NULL)
{
CSingleInstanceB::m_instance = new CSingleInstanceB();//先分配空间,在调用构造函数
}
return m_instance;
}
};
CSingleInstanceB* CSingleInstanceB::m_instance = NULL;//指针必须初始化。
CSingleInstanceB::InnerHelper CSingleInstanceB::ih;//注意使用时,静态局部对象要在外部初始化,否则是无法进入构造和析构函数的。
class CSingleInstanceC
{
private :
CSingleInstanceC() {
std::cout<<"CSingleInstanceC Construct."<<std::endl;
}
~CSingleInstanceC() {
std::cout<<"CSingleInstanceC DeConstruct."<<std::endl;
}
CSingleInstanceC(const CSingleInstanceC& csa){}
public:
static CSingleInstanceC* GetInstance()
{
static CSingleInstanceC m_instance;//第一次使用时才初始化。在有些编译器会加一个变量,判断是否初始化。所以对于局部对象型的是线程不安全的。
return &m_instance;
}
};
正如注释里所说,这种方式在有些编译器中是线程不安全的,因为 像这种 static CSingleInstanceC m_instance;编译器 会自动添加一个bool型的static变量来标记是否已经初始化过了。
if(bInit)
{
static CSingleInstanceC m_instance;
}
很明显这里先判断是否初始化,再去初始化,这两步在多线程环境下是不安全的。会出现两个线程同时去实例化对象。当然在c++11中已经保证了线程安全的。懒汉模式都是线程不安全的,怎么保证线程不安全呢。肯定是加锁了。我们来引入一个加锁的懒汉模式。
class Lock
{
public:
bool DoLock(){};
bool UnLock(){};
};
class CLockWrapper
{
public :
Lock m_lock;
public:
CLockWrapper(Lock& lock):m_lock(lock){
// m_lock.DoLock();
};
bool DoLock()
{
return m_lock.DoLock();
}
~CLockWrapper(){
m_lock.UnLock();//保证无论如何都能释放锁
};
};
class CSingleInstanceD
{
private:
CSingleInstanceD(){ std::cout<<"construct."<<std::endl;};
~CSingleInstanceD(){
std::cout<<"desconstruct."<<std::endl;
};
CSingleInstanceD(const CSingleInstanceD& csb){};
private:
static CSingleInstanceD* m_instance ;//static CSingleInstanceB* m_instance = NULL; //这种方式初始化也是不行的 ,因为 const static 的变量才可以。static属于全局的,必须在外部初始化。
static Lock lock;
public:
static CSingleInstanceD *GetInstance()
{
if(m_instance == NULL)
{
//有异常的锁
if(lock.DoLock())
{
if(m_instance == NULL)
{
m_instance = new CSingleInstanceD();//先分配空间,在调用构造函数.
}
lock.UnLock();//当new抛出异常的时候,锁就会被强占,无法被释放。
}
//包了一层的锁。
CLockWrapper lw(lock);//lockWrapper 的析构函数保证一定能释放锁
if(lw.DoLock())
{
if(m_instance == NULL)
{
m_instance = new CSingleInstanceD();
}
}
}
return m_instance;
}
void DoSomething()
{
std::cout<<" DoSomething..."<<std::endl;
}
};
CSingleInstanceD* CSingleInstanceD::m_instance = NULL;//指针必须初始化。
Lock CSingleInstanceD::lock;
注释已经很清楚了。加锁模式中,通过引入了一个Lock的wrapper类,同样借助了析构函数一定会执行的特性,保证锁一定能被释放。
引入模板来实现一个通用化的单例模式。这里以有锁的懒汉模式为例介绍:
template<class T>
class CSingleInstanceE
{
private:
CSingleInstanceE(){ std::cout<<"construct."<<std::endl;};
~CSingleInstanceE(){
std::cout<<"desconstruct."<<std::endl;
};
CSingleInstanceE(const CSingleInstanceE& csb){};
private:
static CSingleInstanceE* m_instance ;//static CSingleInstanceB* m_instance = NULL; //这种方式初始化也是不行的 ,因为 const static 的变量才可以。static属于全局的,必须在外部初始化。
static Lock lock;
T m_typeinstance;//这里就是包装的模版
public:
static T* GetInstance()
{
if(m_instance == NULL)
{
//有异常的锁
if(lock.DoLock())
{
if(m_instance == NULL)
{
m_instance = new CSingleInstanceE();//先分配空间,在调用构造函数.
}
lock.UnLock();//当new抛出异常的时候,锁就会被强占,无法被释放。
}
//包了一层的锁。
CLockWrapper lw(lock);//lockWrapper 的析构函数保证一定能释放锁
if(lw.DoLock())
{
if(m_instance == NULL)
{
m_instance = new CSingleInstanceE();
}
}
}
return &(m_instance->m_typeinstance);//这里就是包装的模版实例
}
void DoSomething()
{
std::cout<<" DoSomething..."<<std::endl;
}
};
CSingleInstanceE* CSingleInstanceE::m_instance = NULL;//指针必须初始化。
Lock CSingleInstanceD::lock;
完。以上是关于单例的总结。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。