C++设计模式---单例模式

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/shanghx_123/article/details/86633627

设计模式

设计模式(Design Pattern)是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。
目的: 为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化;设计模式是软件工程的基石脉络,如同大厦的结构一样。
分类: 主要分三个类型:创建型、结构型和行为型。
具体分类:
单例模式就属于创建型的设计模式。

单例模式

单例模式即一个类只能创建一个对象,该模式可以保证系统中该类只有一个实例,并提供一个访问它的全局访问点,该实例被所有程序模块共享。比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。

单例模式有两种实现模式:

饿汉模式

饿汉:饿了肯定要饥不择食。所以在单例类定义的时候就进行实例化。 就是说不管你将来用不用,程序启动时就创建一个唯一的实例对象。如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么显然使用饿汉模式来避免资源竞争,提高响应速度更好。

**特点:**由于要进行线程同步,所以在访问量比较大,或者可能访问的线程比较多时,采用饿汉实现,可以实现更好的性能。这是以空间换时间。

class Singleton
{
public:
	//提供创建对象的接口
	static Singleton* GetInstance()
	{
		return m_pInstance;
	}
private:
	//构造函数私有
	Singleton()
	{};
	
	//防拷贝,拷贝构造和赋值私有,只声明不定义
	Singleton(Singleton const &);
	Singleton& operator=(Singleton const &);

	static Singleton* m_pInstance;//单例对象指针
};
Singleton* Singleton::m_pInstance=new Singleton;

因为在饿汉模式下,在单例类定义的时候就已经定义了一个对象,对类进行了初始化。后面不管哪个线程调用成员函数GetInstance(),都只不过是返回一个对象的指针而已。所以是线程安全的,不需要在成员函数GetInstance()中加锁。

缺陷: 如果单例对象构造十分耗时或者占用很多资源,比如加载插件啊, 初始化网络连接啊,读取文件啊等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好

懒汉模式

懒汉:故名思义,不到万不得已就不会去实例化类,也就是说在第一次用到类实例的时候才会去实例化;

懒汉模式(不安全版本)

class Singleton
{
public:
	static Singleton* GetInstance()
	{
		if (nullptr == m_pInstance)
		{
			m_pInstance = new Singleton();	
		}
		return m_pInstance;
	}
private:
	Singleton()
	{}
	//防拷贝
	Singleton(Singleton const&);
	Singleton& operator = (Singleton const&);

	static Singleton* m_pInstance; //单例对象指针
};
Singleton* Singleton::m_pInstance = nullptr;

懒汉模式是类定义的时候先不创建对象,等需要的时候,再调用GetInstance()这个函数接口来创建,所以称之为懒汉模式。这个版本的懒汉是不安全得,因为当多个线程同时执行GetInstance()这个函数的时候,就会创建出两个对象,不符合单例模式的特点。所以需要我们来加锁来保证线程安全。

懒汉模式(加锁,安全版)

#include<iostream>
#include<mutex>
using namespace std;
//懒汉模式
class Singleton
{
public:
	static Singleton* GetInstance()
	{
		//双重判断,防止多次new
		if (nullptr == m_pInstance)
		{
			m_mtx.lock();
			if (nullptr == m_pInstance)
			{
				m_pInstance = new Singleton();
			}
			m_mtx.unlock();
		}
		return m_pInstance;
	}
public:
	//垃圾回收类(内部类)
	class CGarbo
	{
	public:
		~CGarbo()
		{
			if (Singleton::m_pInstance)
			{
				delete Singleton::m_pInstance;
				//释放之后一定指针一定要赋值为nullptr
				m_pInstance = nullptr;
			}
		}
	};
	//定义一个静态变量,程序结束时,系统会自动调用他的析构函数从而释放对象
	static CGarbo Garbo;
private:
	Singleton()
	{ }
	//防拷贝
	Singleton(Singleton const&);
	Singleton& operator = (Singleton const&);

	static Singleton* m_pInstance; //单例对象指针
	static mutex m_mtx;//互斥锁
};
Singleton* Singleton::m_pInstance = nullptr;
Singleton::CGarbo Garbo;
mutex Singleton::m_mtx;
int main()
{
	Singleton* p = Singleton::GetInstance();

	Singleton::CGarbo c;
	//其实可以不用掉析构函数,main函数退出后会自动调用垃圾类的洗析构
	c.~CGarbo();
	system("pause");
	return 0;
}

解释:上面的代码需要注意几个点
1.双重if判断,因为当第一次实例化对象的时候,如果多个线程共同调用GetInstance()函数时,第一个if判断指针是否为空,如果是空,抢到锁的函数会加锁,此时其他线程就需要等待,当它把new执行后,即创建了对象之后,接着会把锁释放,这时其他线程拿到锁后,本来其他线程就不需要再次new了,如果这时不加第二个if,那么其他线程就会再次new,导致创建多个对象。所以需要双重if判判断。

2.第二个就是,垃圾回收的类里面的析构函数,delete完一定要把指针赋值为nullptr,否则的话,如果你定义了c对象,最后你调用析构函数将单例对象的空间释放,接着出了main函数后,会再次调用该对象的析构函数,此时单例对象的指针并没有指向nullptr,所以会再次delete,从而释放两次,程序就会崩掉。
3.其实你的垃圾类的对象c不用掉析构也可以,因为函数退出后,会自动调用c的析构。

猜你喜欢

转载自blog.csdn.net/shanghx_123/article/details/86633627