我是靠谱客的博主 冷艳冥王星,最近开发中收集的这篇文章主要介绍23种常用设计模式之一:单例模式,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

单例模式的概念

单例模式指的是一个类在整个应用程序中只有一份实例。
注意:

  • 1、单例类只有一份实例
  • 2、单例类只能由该类自己创建一份实例。
  • 3、单例类必须提供一个全局的访问点,以供类外获取到这份实例

为什么只能由该类自己创建一份实例 ?
假设有一个全局变量count, 它也是一个对象。如果在一个源文件中使用int count = 0创建了一个对象,在源文件包含的头文件中也使用了int count = 0创建了一个对象。这两个count并不是同一个对象!所以编译的时候会报重定义的错误。类对象的创建也是如此,所以单例类必须由它自己创建一份实例,那么类外就获取不到它的实例,这个类就必须提供一个全局的访问点,以供类外获取到这份实例。

单例模式的两种方式

饿汉模式:事先准备好,程序启动时就创建一个唯一的实例对象。不管以后用还是不用,需要的时候可以直接取用 。


饿汉模式的优点:简单
饿汉模式的缺点

  • 无法控制单例创建初始化顺序
  • 如果单例对象初始化很费时间,那么程序启动就会很慢

懒汉模式:事先没有准备好,需要的时候再做工作。


懒汉模式的优点

  • 能够控制单例创建初始化的顺序
  • 单例对象的初始化不会影响程序启动的快慢。

懒汉模式的缺点:相对于饿汉模式要复杂些,尤其是还要控制线程安全的问题。


懒汉模式代码速览

//饿汉模式的单例模式
class Singleton
{
public:
	//2、提供一个可供全局的访问点,以获取到该实例
	static Singleton& GetInstance()
	{
		cout << "调用一次GetInstance" << endl;
		return _st;
	}
private:
	Singleton()
		:_cap(0)
	{
		printf("Singleton()->%pn", &_cap);
	}
	//将拷贝封死,防止被类外用各种途径创建对象
	Singleton(const Singleton& st) = delete;
	Singleton& operator=(const Singleton& st) = delete;
private:
	queue<int> _que;
	int _cap;

	static Singleton _st; //1、自己创建一个实例
};
Singleton Singleton::_st; //在程序入口前就完成对单例对象的初始化

饿汉模式编写逻辑

下面是一个类的框架,我们要把它改成饿汉式的单例模式

//饿汉模式的单例模式
class Singleton
{
public:
	Singleton()
		:_cap(0)
	{}
private:
	queue<int> _que;
	int _cap;
};

那么首先就需要保证类外不能够创建对象,所以我们把类外,能够创建类对象的渠道封死
1、构造函数私有 2、封死拷贝构造函数 3、封死赋值拷贝函数

//饿汉模式的单例模式
class Singleton
{
public:

private:
	Singleton()
		:_cap(0)
	{
		printf("Singleton()->%pn", &_cap);
	}
	//将拷贝封死,防止被类外用各种途径创建对象
	Singleton(const Singleton& st) = delete;
	Singleton& operator=(const Singleton& st) = delete;
private:
	queue<int> _que;
	int _cap;
};

现在类外已经不能够创建对象了,由单例类自己创建一个实例:

//饿汉模式的单例模式
class Singleton
{
public:

private:
	Singleton()
		:_cap(0)
	{
		printf("Singleton()->%pn", &_cap);
	}
	//将拷贝封死,防止被类外用各种途径创建对象
	Singleton(const Singleton& st) = delete;
	Singleton& operator=(const Singleton& st) = delete;
private:
	queue<int> _que;
	int _cap;

	static Singleton _st; //1、自己创建一个实例
};
Singleton Singleton::_st; //在程序入口前就完成对单例对象的初始化

现在类已经有实例了,需要给类外提供一个全局的访问点,以供类外获得这个对象。

```cpp
//饿汉模式的单例模式
class Singleton
{
public:
	//2、提供一个可供全局的访问点,以获取到该实例
	static Singleton& GetInstance()
	{
		cout << "调用一次GetInstance" << endl;
		return _st;
	}
private:
	Singleton()
		:_cap(0)
	{
		printf("Singleton()->%pn", &_cap);
	}
	//将拷贝封死,防止被类外用各种途径创建对象
	Singleton(const Singleton& st) = delete;
	Singleton& operator=(const Singleton& st) = delete;
private:
	queue<int> _que;
	int _cap;

	static Singleton _st; //1、自己创建一个实例
};
Singleton Singleton::_st; //在程序入口前就完成对单例对象的初始化

懒汉模式代码速览

仅支持C++11标准的懒汉模式代码, 版本一:

//懒汉模式的单例模式
class Singleton
{
public:
	static Singleton& GetInstance()
	{
		cout << "调用了一次GetInstance" << endl;
		//double check
		if (_sPtr == nullptr)
		{
			unique_lock<mutex> lock(_mtx);
			if (_sPtr == nullptr)
			{
				_sPtr = new Singleton;
			}
		}
		return *_sPtr;
	}
private:
	Singleton()
		:_cap(0)
	{
		printf("Singleton()->%pn", &_cap);
	}
	//将拷贝封死,防止被类外用各种途径创建对象
	Singleton(const Singleton& st) = delete;
	Singleton& operator=(const Singleton& st) = delete;
private:
	queue<int> _que;
	int _cap;

	static Singleton* _sPtr;
	static mutex _mtx;
};
mutex Singleton::_mtx;
Singleton* Singleton::_sPtr = nullptr; 

上面的这份版本只需要修改 封死类外创建类对象的途径 的方式, 就是C++98和C++11都支持的版本, 版本二

......
	//将拷贝封死的方式更改为C++98支持的
	Singleton(const Singleton& st);
	Singleton& operator=(const Singleton& st);
......

懒汉模式的另一种优化版本, 版本三:

//懒汉模式的单例模式
class Singleton
{
public:
	static Singleton& GetInstance()
	{
		cout << "调用了一次GetInstance" << endl;
		static Singleton sInst;
		return sInst;
	}
private:
	Singleton()
		:_cap(0)
	{
		printf("Singleton()->%pn", &_cap);
	}
	//将拷贝封死,防止被类外用各种途径创建对象
	Singleton(const Singleton& st) = delete;
	Singleton& operator=(const Singleton& st) = delete;
private:
	queue<int> _que;
	int _cap;
};

在这里插入图片描述
C++98中,static修饰的对象构造初始化不能保证是线程安全的。而C++11对这个问题做了优化,C++11中被static修饰的对象构造初始化一定是线程安全的。所以才会有懒汉模式的版本三。

关于懒汉模式的双重检查

懒汉模式的单例提供了一个全局的访问点:

	static Singleton& GetInstance()
	{
		cout << "调用了一次GetInstance" << endl;
		//double check
		if (_sPtr == nullptr)
		{
			unique_lock<mutex> lock(_mtx);
			if (_sPtr == nullptr)
			{
				_sPtr = new Singleton;
			}
		}
		return *_sPtr;
	}

为什么不能是下面这个样子?

	static Singleton& GetInstance()
	{
		cout << "调用了一次GetInstance" << endl;
		if (_sPtr == nullptr)
		{
			_sPtr = new Singleton;
		}
		return *_sPtr;
	}

在多线程下,它不是线程安全的,所以极大概率会造成下面的情况
在这里插入图片描述
因此需要加锁解锁,来保证线程安全。

	static Singleton& GetInstance()
	{
		cout << "调用了一次GetInstance" << endl;
		unique_lock<mutex> lock(_mtx);
		if (_sPtr == nullptr)
		{
			_sPtr = new Singleton;
		}
		return *_sPtr;
	}

实际上,只有第一次去创建的时候(_sPtr == nullptr)才会有线程安全的问题, 如果使用仅仅像上面这个样子,那么不管是否已经有实例,每一次调用都会涉及加锁和解锁,极大的降低了效率。因此采用双重检查的方式,即可以保证线程是安全的,也提升了效率。

	static Singleton& GetInstance()
	{
		cout << "调用了一次GetInstance" << endl;
		//double check
		if (_sPtr == nullptr)
		{
			unique_lock<mutex> lock(_mtx);
			if (_sPtr == nullptr)
			{
				_sPtr = new Singleton;
			}
		}
		return *_sPtr;
	}

最后

以上就是冷艳冥王星为你收集整理的23种常用设计模式之一:单例模式的全部内容,希望文章能够帮你解决23种常用设计模式之一:单例模式所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(43)

评论列表共有 0 条评论

立即
投稿
返回
顶部