单例模式分懒汉模式和饿汉模式。本文对饿汉子模式中的一些细节进行简要说明。
1、单例类的实例化可以采用堆也可以采用全局区,分别对应指针对象和普通对象。指针对象的地址需要使用static 修饰,所以也是保存在全局区,指针对象是通过new出来的,所以对应类的大小内存是在堆上开辟的。那是使用指针对象还是普通对象呢?
我建议使用普通对象,有两个原因。一是单例类的实例化,一般不释放,而堆却是动态的意思,所以没必要将一般不释放的内存在动态的堆上开辟。二是程序结束时,系统会释放所有的全局变量,包括这里的static普通对象,这样就可以自动调用析构函数处理其它需要关闭文件之类的操作,可以参考后文。
static成员均需要类内声明,类外cpp文件初始化。两者写法如下。
.h文件如下:
class CReject{
public:
static CReject* GetInstance()
{
return spInst;
或
return &Inst;
}
private:
static CReject* spInst;
static CReject Inst;
//static EchReject* spInst = new EchReject;//这是错误的
};
.cpp文件如下
CReject* CReject::spInst = new CReject();//或者new CReject;
CReject CReject::Inst;
new CReject() 和new CReject 没有区别。对于系统内置的类型,比如new int和new int()有区别,带括号具有初始化的作用。
2、保证单例
如何保证单例?先看看不保证单例的设计:
class CReject{
public:
static CReject* GetInstance()
{
return spInst;
}
void PrintValue(){qDebug()<<"PrintValue"<<mValue;}
void SetValue(int v){mValue=v;}
public:
CReject(const CReject&e) //拷贝构造函数
{
qDebug()<<"重写拷贝构造函数";
mValue = e.mValue;
}
CReject& operator=(const CReject&e) //赋值函数
{
qDebug()<<"重写赋值函数";
return *this;
}
public:
CReject(){qDebug()<<"普通构造函数";}
~CReject(){qDebug()<<"析构函数"<<mValue;}
private:
static CReject* spInst;
int mValue;
};
CReject* CReject::spInst = new CReject();
void main(int argc, char *argv[])
{
//在main执行之前,就饿汉子模式实例化了类的指针对象,调用了普通构造函数。
qDebug()<<"main";
CReject *pInst = CReject::GetInstance();
pInst->SetValue(1);
pInst->PrintValue();
CReject e2 = *pInst; //调用拷贝构造函数,程序结束时会调用析构函数
e2.PrintValue(); //打印了拷贝过来的值1
e2.SetValue(2);
e2.PrintValue();
qDebug()<<"朴实的分界线----------";
int buff[10];
CReject *p3 = (CReject*)&buff[0];
*p3 = *pInst; //调用赋值构造函数,程序结束时,不会调用析构函数
CReject e9; //调用普通的构造函数,程序结束时会调用析构函数
e9.SetValue(9);
}
程序输出结果如下。
普通构造函数
main
PrintValue 1
重写拷贝构造函数
PrintValue 1
PrintValue 2
朴实的分界线----------
重写赋值函数
普通构造函数
析构函数 9
析构函数 2
由此,可以看出,将构造函数、拷贝构造函数、赋值构造函数私有化或者将析构函数私有化,就可以保证单例。
private:
CReject(const CReject&e); //仅声明即可
CReject& operator=(const CReject&e) //赋值构造函数,仅声明即可
{
qDebug()<<"重写赋值构造函数";
return *this;
}
CReject(){qDebug()<<"普通构造函数";}
public:
~CReject(){qDebug()<<"析构函数"<<mValue;}
//将构造函数、拷贝构造函数、赋值构造函数私有化,e2 e9 p3都会报错
private:
~CReject(){qDebug()<<"析构函数"<<mValue;}
//将析构函数私有化,声明e2和e9就会报错,但p3不会报错。
CReject *pInst = CReject::GetInstance();
pInst->SetValue(1);
pInst->PrintValue();
// CReject e2 = *pInst; //报错
// e2.PrintValue();
// e2.SetValue(2);
// e2.PrintValue();
qDebug()<<"朴实的分界线----------";
int buff[10];
CReject *p3 = (CReject*)&buff[0];
*p3 = *pInst; //调用赋值构造函数,程序结束时,不会调用析构函数
// CReject e9; //报错
// e9.SetValue(9);
3、针对指针对象,程序结束时如何自动调用析构函数。
方法很多,这里介绍最常用的。先看看不调用析构的设计。
class CReject{
public:
static CReject* GetInstance()
{
return spInst;
}
void PrintValue(){qDebug()<<"PrintValue"<<mValue;}
void SetValue(int v){mValue=v;}
private:
CReject(const CReject&e);
CReject& operator=(const CReject&e);
CReject(){qDebug()<<"普通构造函数";}
//CReject(); //构造函数不能仅声明,因为有实例化
public:
~CReject(){qDebug()<<"析构函数"<<mValue;}
private:
static CReject* spInst;
int mValue;
};
CReject* CReject::spInst = new CReject();
void main(int argc, char *argv[])
{
qDebug()<<"main";
CReject *pInst = CReject::GetInstance();
pInst->SetValue(1);
pInst->PrintValue();
}
程序没有调用析构函数,运行结果:
普通构造函数
main
PrintValue 1
内嵌类的实例对象。内嵌类是为了防止其他人使用这个类,全部的实例对象在程序结束时会被析构。利用这个特点,来析构我们想析构的对象。
class CReject{
public:
static CReject* GetInstance()
{
return spInst;
}
void PrintValue(){qDebug()<<"PrintValue"<<mValue;}
void SetValue(int v){mValue=v;}
private:
CReject(const CReject&e);
CReject& operator=(const CReject&e);
CReject(){qDebug()<<"普通构造函数";}
public:
~CReject(){qDebug()<<"析构函数"<<mValue;}
private:
class CRejectDistruct{ //新增一个内嵌类
public:
~CRejectDistruct(){
if(spInst != NULL)
{
qDebug()<<"~CRejectDistruct";
delete spInst; //析构我们的指针对象
}
}
};
private:
static CReject* spInst;
static CRejectDistruct sdis; //内嵌类的实例化声明
int mValue;
};
CReject* CReject::spInst = new CReject();
CReject::CRejectDistruct CReject::sdis; //必须要有初始化的操作,static的仅声明不行
void main(int argc, char *argv[])
{
qDebug()<<"main";
CReject *pInst = CReject::GetInstance();
pInst->SetValue(1);
pInst->PrintValue();
}
程序结束时,调用了析构函数,运行结果:
普通构造函数
main
PrintValue 1
~CRejectDistruct
析构函数 1
4、补充一个做这个文章时,中间遇到的类实例化问题。
class CR
{
public:
int mv;
static CR* spInst;
CR& operator=(const CR &e){qDebug()<<"赋值构造函数";}
~CR(){qDebug()<<"~CR";}
};
CR* CR::spInst = new CR();
//情况1
CR *pcr; //非法操作,这里使用了栈上的野指针
qDebug()<<pcr->mv;
pcr->mv = 5;
qDebug()<<pcr->mv;
//情况2
int buff[10];
CR *pcrInst = CR::spInst;
CR *pcr = (CR*)&buff[0]; //并没有实例化类,只是栈上指针的强制类型转换
*pcr = *pcrInst; //会调用赋值构造函数,程序结束时不调用析构函数
qDebug()<<pcr->mv;
pcr->mv = 5;
qDebug()<<pcr->mv;
//情况3
CR *pcrInst = CR::spInst;
CR *pcr;
pcr = pcrInst; //合法操作,一个指针类型赋值给另一个指针类型
qDebug()<<pcr->mv;
pcr->mv = 5;
qDebug()<<pcr->mv;