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

本文探讨了C++中的单例模式,强调了全局变量在多线程程序中的潜在问题,并介绍了单例模式作为解决方案的重要性。单例模式确保类只有一个实例并提供全局访问点。文章讨论了不同实现方法,包括考虑资源释放的静态对象和RAII机制。此外,还列举了多个实际应用案例,如任务管理器、回收站、日志应用和数据库连接池,以展示单例模式在系统设计中的价值。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

       根据Google的C++编码规范,全局变量在项目中是能不用就不用的,它是一个定时炸弹,是一个不安全隐患,特别是在多线程程序中,会有很多的不可预测性;同时,使用全局变量,也不符合面向对象的封装原则,所以,在纯面向对象的语言Java和C#中,就没有纯粹的全局变量。所以就有了单例模式:

何为单例模式,在GOF的《设计模式:可复用面向对象软件的基础》中是这样说的:保证一个类只有一个实例,并提供一个访问它的全局访问点。

首先,需要保证一个类只有一个实例;

在类中,要构造一个实例,就必须调用类的构造函数,如此,为了防止在外部调用类的构造函数而构造实例,需要将构造函数的访问权限标记为protected或private;

最后,需要提供要给全局访问点,就需要在类中定义一个static函数,返回在类内部唯一构造的实例。

全局变量在项目中是能不用就不用的,它是一个定时炸弹,是一个不安全隐患,特别是在多线程程序中,会有很多的不可预测性;同时,使用全局变量,也不符合面向对象的封装原则,所以,在纯面向对象的语言Java和C#中,就没有纯粹的全局变量。那么,如何完美的解决这个问题,就需要引入设计模式中的单例模式。

Singleton 模式其实是对全局静态变量的一个取代策略,
1)仅有一个实例,提供一个类的静态成员变量,大家知道类的静态成员变量对于一个类的所有对象而言是惟一的
2)提供一个访问它的全局访问点,也就是提供对应的访问这个静态成员变量的静态成员函数,对类的所有对象而言也是惟一的.
在 C++中,可以直接使用类域进行访问而不必初始化一个类的对象.

下面是一个单例模式的UML类图:


单例模式,单从UML类图上来说,就一个类,没有错综复杂的关系。但是,在实际项目中,使用代码实现时,还是需要考虑很多方面的。

第一种实现方法:

#include<iostream>
using namespace std;



class Singleton
{
public:
	static Singleton *GetInstance_Point()
	{
		if (My_instance == NULL)
		{
			My_instance = new Singleton;
		}
		return My_instance;
	}
	static Singleton GetInstance()
	{
		return *GetInstance_Point();
	}

	void Getval()
	{
		cout << m_data << endl;
	}

private:
	Singleton(int data = 10):m_data(data)
	{}
	static Singleton *My_instance;

	int m_data;
};

 Singleton *Singleton::My_instance = NULL;

int main()
{
	Singleton *p = Singleton::GetInstance_Point();
	Singleton *q = Singleton::GetInstance_Point();
	if (p == q)
	{
		cout << "OK" << endl;
	}
	else
	{
		cout << "Falied" << endl;
	}

	getchar();
	return 0;
}

但是上边的单例模式的实现没有考虑到多线程的情况 ,在多线程的情况下也会出现创建多个实例的问题,所以现在问题就集中在创建实例的临界区,当然我们肯定想到要加锁,所以就是下边的代码

#include<iostream>
using namespace std;



class Singleton
{
public:
	static Singleton *GetInstance_Point()
	{
		if (My_instance == NULL)
		{
			Lock();//C++没有锁机制  可以使用boost库的锁机制,这里只是说明一下
			My_instance = new Singleton;
			UnLock();
		}
		return My_instance;
	}
	static Singleton GetInstance()
	{
		return *GetInstance_Point();
	}

	void Getval()
	{
		cout << m_data << endl;
	}

private:
	Singleton(int data = 10):m_data(data)
	{}
	static Singleton *My_instance;

	int m_data;
};
Singleton *Singleton::My_instance = NULL;

int main()
{

return 0;
}

但是,如果进行大数据的操作,加锁操作将成为一个性能的瓶颈;为此,一种新的单例模式的实现也就出现了。


#include<iostream>
using namespace std;



class Singleton
{
public:
	static Singleton *GetInstance_Point()
	{
		return My_instance;
	}
	static Singleton GetInstance()
	{
		return *GetInstance_Point();
	}

	void Getval()
	{
		cout << m_data << endl;
	}

private:
	Singleton(int data = 10):m_data(data)
	{}
	static Singleton *My_instance;

	int m_data;
};
Singleton *Singleton::My_instance = new Singleton;

int main()
{

return 0;
}

因为静态初始化在程序开始时,也就是进入主函数之前,由主线程以单线程方式完成了初始化,所以静态初始化实例保证了线程安全性。在性能要求比较高时,就可以使用这种方式,从而避免频繁的加锁和解锁造成的资源浪费。

到了这里我们已经在创建实例的性能上优化到了极致,但是我么创建了实例也要删除实例的,对于前边的例子,对象中的数据成员是POD类型的,也可以理解为内置类型,这些类型的空间可以不显示的使用析构函数析构,

但是对于数据成员是指针类型的或在类中,有一些文件锁了,文件句柄,数据库连接等等,这些随着程序的关闭而不会立即关闭的资源,必须要在程序关闭前,进行手动释放,那就要调用显示的的析构函数进行析构。

#include<iostream>
using namespace std;



class Singleton
{
public:
	static Singleton *GetInstance_Point()
	{
		return My_instance;
	}
	static Singleton GetInstance()
	{
		return *GetInstance_Point();
	}
	static void DestoryInstance()
	{
		if (My_instance != NULL)
		{
			delete My_instance;
			My_instance = NULL;
			//这里如果m_data数据类型是指针或者其他要进行释放,这里是int型就不用了
		}
	}
	void Getval()
	{
		cout << m_data << endl;
	}

private:
	Singleton(int data = 10):m_data(data)
	{}
	static Singleton *My_instance;

	int m_data;
};
Singleton *Singleton::My_instance = new Singleton;

int main()
{
	Singleton *p = Singleton::GetInstance_Point();
	Singleton *q = Singleton::GetInstance_Point();
	if (p == q)
	{
		cout << "OK" << endl;
	}
	else
	{
		cout << "Falied" << endl;
	}
	Singleton::DestoryInstance();
	getchar();
	return 0;
<span style="font-size:18px;">}</span>
在上述的实现中,是添加了一个DestoryInstance的static函数,这也是最简单,最普通的处理方法了;但是,很多时候,我们是很容易忘记调用DestoryInstance函数,就像你忘记了调用delete操作一样。由于怕忘记delete操作,所以就有了智能指针;那么,在单例模型中,没有“智能单例”,该怎么办?怎么办?

这个时候我么就要使用静态对象或者全局对象帮助我们自动调用析构函数

#include<iostream>
using namespace std;



class Singleton
{
public:
	static Singleton *GetInstance_Point()
	{
		return My_instance;
	}
	static Singleton GetInstance()
	{
		return *GetInstance_Point();
	}
	static void DestoryInstance()
	{
		if (My_instance != NULL)
		{
			delete My_instance;
			My_instance = NULL;
			//这里如果m_data数据类型是指针或者其他要进行释放,这里是int型就不用了
		}
	}
	void Getval()
	{
		cout << m_data << endl;
	}

private:
	Singleton(int data = 10):m_data(data)
	{}
	static Singleton *My_instance;

	int m_data;

	class Deletor
	{
	public:
		~Deletor()
		{
			Singleton::DestoryInstance();
		}
	};
	static Deletor dele;
};
Singleton *Singleton::My_instance = new Singleton;
 
Singleton::Deletor Singleton::dele;

int main()
{
	Singleton *p = Singleton::GetInstance_Point();
	Singleton *q = Singleton::GetInstance_Point();
	if (p == q)
	{
		cout << "OK" << endl;
	}
	else
	{
		cout << "Falied" << endl;
	}
	getchar();
	return 0;
}

在程序运行结束时,系统会调用Singleton的静态成员dele的析构函数,该析构函数会进行资源的释放,而这种资源的释放方式是在程序员“不知道”的情况下进行的,而程序员不用特别的去关心,使用单例模式的代码时,不必关心资源的释放。那么这种实现方式的原理是什么呢?我剖析问题时,喜欢剖析到问题的根上去,绝不糊涂的停留在表面。由于程序在结束的时候,系统会自动析构所有的全局变量,实际上,系统也会析构所有类的静态成员变量,就像这些静态变量是全局变量一样。我们知道,静态变量和全局变量在内存中,都是存储在静态存储区的,所以在析构时,是同等对待的。

由于此处使用了一个内部Deletor类,而该类的作用就是用来释放资源,而这种使用技巧在C++中是广泛存在的,这就是C++中的RAII机制。


实际的应用场景有哪些呢?以下,我将列出一些就在咱们周边和很有意义的单例应用场景。
1. Windows的Task Manager(任务管理器)就是很典型的单例模式(这个很熟悉吧),想想看,是不是呢,你能打开两个windows task manager吗? 不信你自己试试看哦~ 
2. windows的Recycle Bin(回收站)也是典型的单例应用。在整个系统运行过程中,回收站一直维护着仅有的一个实例。
3. 网站的计数器,一般也是采用单例模式实现,否则难以同步。
4. 应用程序的日志应用,一般都何用单例模式实现,这一般是由于共享的日志文件一直处于打开状态,因为只能有一个实例去操作,否则内容不好追加。
5. Web应用的配置对象的读取,一般也应用单例模式,这个是由于配置文件是共享的资源。
6. 数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源。数据库软件系统中使用数据库连接池,主要是节省打开或者关闭数据库连接所引起的效率损耗,这种效率上的损耗还是非常昂贵的,因为何用单例模式来维护,就可以大大降低这种损耗。
7. 多线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。
8. 操作系统的文件系统,也是大的单例模式实现的具体例子,一个操作系统只能有一个文件系统。
9. HttpApplication 也是单位例的典型应用。熟悉ASP.Net(IIS)的整个请求生命周期的人应该知道HttpApplication也是单例模式,所有的HttpModule都共享一个HttpApplication实例.
 
总结以上,不难看出:
  单例模式应用的场景一般发现在以下条件下:
  (1)资源共享的情况下,避免由于资源操作时导致的性能或损耗等。如上述中的日志文件,应用配置。
  (2)控制资源的情况下,方便资源之间的互相通信。如线程池等。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值