一、前言
单例模式是程序设计中一种非常常见的设计模式,在面向对象编程的时候,对于某一个类的实例对象,如果我们为了不频繁的创建和销毁它并且全局都使用这一个实例,那么我们就可以将其设计为单例模式,单例模式在实际应用场景还是比较多的,比如我们使用的鼠标箭头在系统运行过程中只能有一个,再比如我们设计登录框按钮的时候,即使多次单击也必须出现一个登录框实例。
单例模式所涉及的知识点当前先将其归纳为3点:
- 单例模式最基本的设计方法 既然我们要保证将class设计成单例模式,那么其必然利用了一定的设计方法才能保证当创建多个对象实例的时候保证返回的是同一个实例,而且也有一些明显的设计特征。
- 线程安全问题 这个问题是很自然而然的,由于其是单例模式,也就是整个程序运行周期只有一个实例,那么如果我两个甚至多个线程同时创建第一个实例的时候(因为必须得有第一个实例被创建出来),到底如何确保这个实例“唯一”呢?
- 资源的回收问题 这个问题其实并不算单例模式特有的问题,但是对于这多个用户同时使用的“仅有”的一个实例如果结束生命周期之后如何对其申请的资源进行合理的释放也是值得关注的一个点。
二、单例模式最基本的设计方法
根据前面的描述,单例模式的类的设计应该有以下几个原则:只能有一个实例,类自行创建这个实例,向外界提供创建的这个实例。至于为什么需要自行创建呢?如果我们允许用户使用 类名:对象名
的创建方式创建实例,那么肯定不能保证“单例”,这也就是说我们不能给予用户构造函数的访问权限,构造函数必须私有;如何向外界提供这个实例呢?用户不能使用构造函数构造对象,所以只能使用类的静态方法得到对象,因为不能通过非静态方法得到对象,只能通过静态方法得到,众多周知,静态方法属于类而不属于某个具体的对象。通过上面的讨论,我们可以得到单例模式的两个必要特征:构造函数私有化&&通过静态函数提供实例。
由于类通过静态函数往外界传送对象,那么类中必须定义一个静态的、指向自身的对象指针作为类中的成员变量
class SingleInstance{
private:
SingleInstance(){} //构造函数私有化,不能别外界访问
static SingleInstance* _single; //指向自身的静态对象指针成员变量
public:
...
static SingleInstance* getInstance(){ //通过静态成员函数向外界提供实例
return _single;
}
...
};
基本的设计原则就如上所示,总结一下就是:
- 构造函数私有化
- 指向自身的静态对象指针成员变量
- 通过静态成员函数向外界提供实例
那么既然有了指向自身的静态对象指针成员变量_single
,那么何时为其分配内存构造出真正的对象实例呢?这里就引出了两种实现方式:懒汉式和饿汉式,也就是我们等到使用到这个实例的时候再去构造还是先构造出来以备不时之需呢?
1、饿汉式
饿汉式顾名思义就是比较饥饿,肯定想先把实例构造出来,代码如下:
class SingleInstance{
private:
SingleInstance(){} //构造函数私有化,不能别外界访问
static SingleInstance* _single; //指向自身的静态对象指针成员变量
public:
...
static SingleInstance* getInstance(){ //通过静态成员函数向外界提供实例
return _single;
}
...
};
//类外初始化要带着类型SingleInstance*
SingleInstance* SingleInstance::_single = new SingleInstance();
这里有一个小点要注意一下:静态成员变量的初始化要放在类外
饿汉式设计方式的好处是类一被加载实例就被构造出来,这样就不用担心多线程都去获取实例而造成创建多个实例的问题了,但是这样不符合编程规范,因为如果从始至终没使用这个实例,那么就白白浪费了内存空间。
2、懒汉式
懒汉式的意思就是我比较懒,啥时候需要的时候我再去办,也就是在外界请求获取实例的时候再去创建实例,程序设计如下:
class SingleInstance{
private:
SingleInstance(){} //构造函数私有化,不能别外界访问
static SingleInstance* _single; //指向自身的静态对象指针成员变量
public:
...
static SingleInstance* getInstance(){ //通过静态成员函数