以C/C++语法浅谈二十三种设计模式(二)——单例模式(Singleton)

本文深入探讨了单例模式的概念及其实现方式,包括懒汉模式和饿汉模式,并讨论了多线程环境下单例模式的线程安全性问题。

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

一.前言

单例模式(Singleton),是常用的设计模式中最常用而且最简单的设计模式。在某些项目开发中,有些类对象在整个工程项目中频繁出现使用,这对于一些大型的类对象,多次申请内存将是一笔很大的系统开销;另外,由于C/C++语言的特性,在我们new出一块内存之后,就意味着在使用完之后释放内存,如果对某个类对象频繁的申请内存,在内存管理方面也很容易出问题。在这种情况下,我们的单例模式“闪亮登场”了。单例模式保证在软件的开始到结束只有一个类对象存在。
单例模式就是一个类只能被实例化一次 ,更准确的说是只能有一个实例化的对象的类。

二.代码实例

我们首先创建一个单例模式的类,如下:

class SingleTon
{
public:
	SingleTon(){}
	~SingleTon(){}
};

在使用的时候我们有下面两种方法实例化这个单例对象:

int _tmain(int argc, _TCHAR* argv[])
{
	SingleTon singleTon1;
	SingleTon* singleTon2 = new SingleTon();

   std::cout << "singleTon1地址:" << singleTon1 << std::endl;
	std::cout << "singleTon2地址:" << singleTon2 << std::endl;
	return 0;
}

输出结果为:
singleTon1地址:0x47FC27
singleTon2地址:0x47FC18
很明显这两个单例对象地址不一样,也就是这个单例对象呗实例化了两次,在通过上面的方法进行实例化单例对象时都会调用单例对象的构造函数,如果将单例对象的构造函数定义为private或者protected:

`class SingleTon
{
public:
	~SingleTon(){}
private:
	SingleTon(){}
};`

这样单例对象无法从外部进行调用。那么既然构造函数是私有的,那么他就只能被类内部的成员函数调用,所以我们可以搞一个共有函数去供外部调用,然后这个函数返回一个对象,为了保证多次调用这个函数返回的是一个对象,我们可以把类内部要返回的对象设置为静态的,就有了下面的代码:

class SingleTon
{
public:
	~SingleTon(){}

	static SingleTon* GetSingTonInstance()
	{
		if(s_pSingleTon == NULL)
		{
			s_pSingleTon = new SingleTon();
		}
		return s_pSingleTon;
	}
private:
	SingleTon(){}

private:
	static SingleTon* s_pSingleTon;
};
SingleTon* SingleTon::s_pSingleTon = NULL;

然后在主函数里进行测试:

int _tmain(int argc, _TCHAR* argv[])
{
	SingleTon* singleTon1 = SingleTon::GetSingTonInstance();
	SingleTon* singleTon2 = SingleTon::GetSingTonInstance();
    
	std::cout << "singleTon1地址:" << singleTon1 << std::endl;
	std::cout << "singleTon2地址:" << singleTon2 << std::endl;

	system("pause");
	return 0;
}

输出结果为:
singleTon1地址:0x267588
singleTon2地址:0x267588
输出的两个对象的地址一样,证明我们的单例对象类是正确的,原理就是在第一次调用SingleTon::GetSingTonInstance()方法时,s_pSingleTon指针变量是空的,所以会创建出一个对象,第二次调用时,静态变量对象不为空,之间返回第一次的对象指针。

考虑这种情况,在多线程调用的时候,如果两个或者多个线程同时对SingleTon::GetSingTonInstance()方法进行调用,结果要么出现对个对象,要么一个对象都没有,因此必须考虑加锁处理。上述这种方法是在我们需要创建的时候进行判断,如果对象为空,我们才进行实例化,好懒啊,所以因为“懒”的原因才会出现多线程不安全的隐患,需要加锁处理,这种方法也叫做“懒汉模式”。
懒汉模式在多线程调用里是不安全的,可用加锁处理消除隐患

那么有没有勤快的方法呢?答案是肯定的,像下面这样,我们在初始化的时候就给静态对象进行实例化:

class SingleTon
{
public:
	~SingleTon(){}

	static SingleTon* GetSingTonInstance()
	{
		return s_pSingleTon;
	}
private:
	SingleTon(){}

private:
	static SingleTon* s_pSingleTon;
};
SingleTon* SingleTon::s_pSingleTon = new SingleTon();

在静态对象指针初始化的时候就进行实例化的创建,在还没有调用之前就创建好了,如饥似渴的在构造之前已经完成了实例化,“饿死了”,所以又叫“饿汉模式”。当然饿汉模式是线程安全的模式。

三.总结

单例对象适用于在开发过程中多次调用的对象,一般情况下,单例对象我们一般设置为智能指针,像C++自己封装的智能智能指针,Boot库里的智能指针,Qt里的智能指针等等。使用智能指针的好处是保证单例对象析构的安全性。

[上一篇:] 以C/C++语法浅谈二十三种设计模式(一)——工厂模式(Factory Method) https://blog.youkuaiyun.com/weixin_39951988/article/details/85758009

【下一篇:】以C/C++语法浅谈二十三种设计模式(三)——代理模式(Proxy)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

尘海折柳

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值