C++单例模式(Singleton Pattern)

目录

单例模式

介绍

一.延时加载——懒汉模式

1.适用于单线程模式下的懒汉模式

2.适用于多线程下的懒汉模式

二.在懒汉模式基础上衍生的双重锁模式

三.贪婪加载——饿汉模式


单例模式

单例模式(Singleton Pattern)是面向对象语言例如C++ 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

注意:

  • 1、单例类只能有一个实例。
  • 2、单例类必须自己创建自己的唯一实例。
  • 3、单例类必须给所有其他对象提供这一实例。

介绍

意图:保证一个类仅有一个实例,并提供一个访问它的全局访问点。

主要解决:一个全局使用的类频繁地创建与销毁。

何时使用:当您想控制实例数目,节省系统资源的时候。

如何解决:判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

关键代码:构造函数是私有的。

应用实例:

  • 1、一个班级只有一个班主任。
  • 2、Windows 是多进程多线程的,在操作一个文件的时候,就不可避免地出现多个进程或线程同时操作一个文件的现象,所以所有文件的处理必须通过唯一的实例来进行。
  • 3、一些设备管理器常常设计为单例模式,比如一个电脑有两台打印机,在输出的时候就要处理不能两台打印机打印同一个文件。

优点:

  • 1、在内存里只有一个实例,减少了内存的开销,尤其是频繁的创建和销毁实例(比如管理学院首页页面缓存)。
  • 2、避免对资源的多重占用(比如写文件操作)。

缺点:没有接口,不能继承,与单一职责原则冲突,一个类应该只关心内部逻辑,而不关心外面怎么样来实例化。

常用的C++单例设计方案有以下几种:

  • 延时加载——懒汉模式
    • 适用于单线程下的懒汉模式
    • 适用于多线程下的懒汉模式
  • 在懒汉模式基础上衍生的双重锁模式
  • 贪婪加载——饿汉模式

设计思想:

  • 将能够生成对象的函数接口屏蔽,即将构造函数和拷贝构造函数放在私有数据成员下。
  • 对外提供一个接口,这个接口会返回一个唯一生成的对象,且无论调用这个接口多少次,返回的均是同一个对象。这里还需要注意,因为单例模式要求只能实例化一个对象,而当调用普通的类成员方法时需要依赖对象来调用,因此我们需要将这个接口设置为静态方法,即给这个方法加上关键字static

一.延时加载——懒汉模式

顾名思义,懒汉模式就是非常“懒”,只有当我们需要的时候才实例化唯一的对象

1.适用于单线程模式下的懒汉模式

class SingleTon
{
public:

	static SingleTon* getInstance()//提供唯一实例化对象的接口
	{
		if (instance== NULL)
		{
			instance= new SingleTon();
		}

		return instance;
	}
private:

	SingleTon(){}			//构造函数
	SingleTon(const SingleTon&);//拷贝构造函数
	static SingleTon* instance;	
};
SingleTon* SingleTon::instance= NULL;//静态成员变量必须在类外定义

我们可以看到,每一次调用接口时都会判断instance是否为NULL,只有为NULL时才会创建对象,这样就避免了重复实例化多个对象,且每次调用接口后的返回的对象都是同一个对象。

但这种模式只适用于单线程模式,例如若某一个进程中的两个线程A和B,如果当线程A执行到 if(instance==NULL) 这一步时,得知此时instance==NULL,那么紧接着它就要执行 instance = new SingleTon()来实例化唯一对象了,但此事线程B也执行到了if(instance==NULL)这一步,且此事线程A还没来得及实例化唯一对象,那么线程B也会执行 instance = new SingleTon()来实例化唯一对象,这样就会引发问题,违背了单例模式的设计原则。

为了解决这一问题我们提出一种适用于多线程下的懒汉模式。

2.适用于多线程下的懒汉模式

所谓适用于多线程下的懒汉模式就是在判断instance是否为NULL时,需要加锁,加锁的目的是为了限制不同线程对同一资源的使用顺序,从而达到同步的目的,判断完毕后再解锁。

class SingleTon
{
public:
	static SingleTon* getInstance()
	{
		lock();	//加锁
		if (instance== NULL)
		{
			instance= new SingleTon();	//堆区申请对象
		}
		unlock(); //解锁
		
		return instance;
	}
private:
	SingleTon(){}			//构造函数
	SingleTon(const SingleTon&);//拷贝构造函数
	static SingleTon* instance;	
};
SingleTon* SingleTon::instance= NULL;//静态成员变量必须在类外定义

这种情况下,当线程A在判断instance是否为NULL前,先要进行加锁操作(lock),此时若线程B也执行到了lock()这一步,他发现此时临界资源已被加锁,且无法再进行加锁,那么此时线程B就被阻塞住了,它要等到锁被释放,即线程A执行unlock()以后才能继续执行,这样我们就解决了由多线程引起的重复实例化对象的问题。

二.在懒汉模式基础上衍生的双重锁模式

与其把这种模式称为 在懒汉模式基础上衍生的双重锁模式 ,不如把它称为 适用于多线程下的懒汉模式——改进版。

我们看到在适用于多线程下的懒汉模式中,每一个线程在判断instance是否为NLL前,都先要进行加锁操作,如果此时已经有其他线程进行了加锁操作,那么当前线程就进入了阻塞状态,如此以往,就会导致大量资源被浪费,还影响了效率,其实我们只需要小小的改动,就可以解决这个问题。

class SingleTon
{
public:
	static SingleTon* getInstance()
	{
		if (instance== NULL)		
		{
			lock();
			if (instance== NULL)
			{
				instance= new SingleTon();
			}
			unlock();
		}
		return instance;
	}
private:
	SingleTon(){}
	SingleTon(const SingleTon&);
	static SingleTon* instance;
};

我们看到,我们只是将加锁和判断instance是否为NULL 的顺序对调了一下就解决了这个问题,即当instance==NULL的时候,才去进行加锁,实例化唯一对象,解锁的操作,如果此时instance != NULL,说明唯一的对象已经被实例化出来了,那么其他线程就不需要再进行加锁,解锁操作了,而是可以直接使用这个唯一的对象。

三.贪婪加载——饿汉模式

所谓的饿汉模式与懒汉模式恰恰相反,懒汉模式是只有当我们需要的时候才实例化唯一的对象,而饿汉是无论你需不需要,一开始就将唯一的对象实例化出来。

class SingleTon
{
public:
	static SingleTon* getInstance()
	{
		return instance;
	}
private:
	SingleTon(){}
	SingleTon(const SingleTon&);
	static SingleTon* instance;
};
SingleTon* SingleTon::instance= new SingleTon();//静态成员必须在类外定义

我们发现饿汉模式的好处,就是不需要再判断instance是否为NULL了,这在某些情况下能起到非常大的作用。

发布了88 篇原创文章 · 获赞 40 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/ThinPikachu/article/details/104554964