23种常用设计模式之一:单例模式

23种常用设计模式之一:单例模式,第1张

单例模式的概念

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

  • 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()->%p\n", &_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()->%p\n", &_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()->%p\n", &_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()->%p\n", &_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()->%p\n", &_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()->%p\n", &_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;
	}

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/3002909.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-09-27
下一篇 2022-09-27

发表评论

登录后才能评论

评论列表(0条)

保存