c++单例模式

本文详细介绍了C++中单例模式的两种实现方式——饿汉和懒汉模式,并讨论了线程安全问题。饿汉模式在类加载时即创建实例,保证线程安全但可能造成资源浪费;懒汉模式则延迟实例化,但在多线程环境下需要额外的同步措施以确保线程安全。同时,提出了改进的懒汉模式,减少不必要的锁操作以提高效率。此外,文章还探讨了单例模式的适用场景及其在资源管理和控制访问上的优势。
摘要由CSDN通过智能技术生成

单例模式:

单例大约有两种实现方法:懒汉与饿汉。
懒汉:故名思义,不到万不得已就不会去实例化类,也就是说在第一次用到类实例的时候才会去实例化,
饿汉:饿了肯定要饥不择食。所以在单例类定义的时候就进行实例化。
(1)饿汉

饿汉单例,即在最开始的时候,静态对象就已经创建完成;
设计方法是类中包含一个静态成员指针,该指针指向该类的一个对象,提供一个公有的静态成员方法,返回该对象指针;为了使得对象唯一,还需要将构造函数设为私有,代码如下:

class Sigletion
{
	//构造函数为私有;
 
	Sigletion()
	{
		cout << "Sigletion()" <<endl;
	}
 
	static Sigletion * intance; //静态成员,指向Sigletion对象的指针。
 
public:
	//提供静态共有方法,可以使用类名加域名进行访问,返回对象指针;
	static Sigletion* GetSigletion()
	{
		return intance;
	}
};
 
Sigletion* Sigletion:: intance = new Sigletion;

测试代码:

int main()
{
	/*我们注意到,在刚进入main函数,我们就打印了一句“”main begin“”,为什么要在这里加入一句输出语句呢?
	因为我们此时的单例模式采用的是饿汉单例,所以,哪怕你还没用到我这个对象,我也会先创建一个出来,
	先占着,即所谓的饿汉,其实就是类似于全局变量的构造是在进入main函数之前的原理,*/
 
	cout << "main begin" <<endl; //验证静态对象的创建在main之前。
 
	//验证无法创建实例。
	//Sigletion tmp;
	//Sigletion *ptr = new Sigletion;
 
	//调用共有的静态成员方法
	Sigletion *ptr = Sigletion::GetSigletion();
	Sigletion *ptr2 = Sigletion::GetSigletion();
	if (ptr == ptr2)
	{
		cout << "yes" <<endl;
	}
	else
	{
		cout << "no"<<endl;
	}
 
	cout << "hello..." <<endl;
	system("pause");
	return 0;
}

单例的饿汉实现是线程安全的,因为对象在使用前就已经创建出来了

(2)懒汉:

所谓懒汉模式,就是尽可能晚的创建这个对象的实例,即在单例类第一次被引用时将自己初始化;其实C++里很多地方都是类似这样的思想,比如晚绑定,写时拷贝技术等,就是尽量使资源的利用最大化,不要让空闲的人还占着有限的资源。

class Sigletion2
{
	Sigletion2()
	{
		cout << "Sigletion2()" <<endl;
	}
 
	static Sigletion2* intance2;
 
public:
 
	static Sigletion2* GetSigletion2()
	{
		if (intance2 == NULL)
		{
			intance2 = new Sigletion2();
			cout << "it is once" <<endl;
		}
		else
		{
			cout << "it is not once" <<endl;
		}
		return intance2;
 
	}
 
};
 
Sigletion2* Sigletion2:: intance2 = NULL;  //先初始化为空,等真正用上这个单例的时候再创建这个例。

(3)懒汉的线程安全问题:

如果此时多线程进行操作,简单点以两个线程为例,假设pthread_1刚判断完 intance 为NULL 为真,准备创建实例的时候,切换到了pthread_2, 此时pthread_2也判断intance为NULL为真,创建了一个实例,再切回pthread_1的时候继续创建一个实例返回,那么此时就不再满足单例模式的要求了, 既然这样,是因为多线程访问出的问题,那我们就来加把锁,使得线程同步;

class singleton
{
private:
	singleton()
	{
		pthread_mutex_init(&mutex);
	}
 
	static singleton* p;
	static pthread_mutex_t mutex;
public:
	static singleton* initance()
	{
		pthread_mutex_lock(&mutex);
		if (p == NULL)
		{
			p = new singleton();
		}
		pthread_mutex_unlock(&mutex);
 
		return p;
	}
};
 
pthread_mutex_t singleton::mutex;
singleton* singleton::p = NULL;

那么我们这样写的代码是没有问题的,但是效率可能有点低,因为加锁是一个非常耗时的操作,没有必要每次都要加锁解锁,只有刚开始创建对象的时候需要加锁。如果对象都
创建出来了,就没必要加锁解锁了,直接返回这个对象的指针。

class singleton
{
private:
	singleton()
	{
		pthread_mutex_init(&mutex);
	}
 
	static singleton* p;
	static pthread_mutex_t mutex;
public:
	static singleton* initance()
	{
		if (p == NULL)   //p != NULL,说明对象已经创建出来了,直接返回对象的指针,没必要在加锁解锁浪费时间。
		{
			pthread_mutex_lock(&mutex);
			if (p == NULL)
			{
				p = new singleton();
			}
			pthread_mutex_unlock(&mutex);
		}
		return p;
	}
};
 
pthread_mutex_t singleton::mutex;
singleton* singleton::p = NULL;

允许可变数目的实例,基于单例模式我们可以扩展,使用与单例控制相似的方法来获得指定个数的对象实例。(即单例类内有多个静态对象指针成员,每次当单例类被引用时随机分配一个实例对象);

单例模式的适用场景
(1)系统只需要一个实例对象,或者考虑到资源消耗的太大而只允许创建一个对象。
(2)客户调用类的单个实例只允许使用一个公共访问点,除了该访问点之外不允许通过其它方式访问该实例 (就是共有的静态方法)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值