设计模式之【单例模式】

目录

什么是单例模式?

为什么用单例模式?

如何实现单例模式?

要点

饿汉式

懒汉式

加锁

双检锁


什么是单例模式?

在程序运行过程中,一个类只能实例化一个对象

为什么用单例模式?

有些模块在一个程序中只能存在一个实例,例如像操作系统中的任务管理器,如果不使用单例模式

需要考虑以下三个问题:

  1. 多个任务管理器是否有意义?因为任务管理器的内容是一样的
  2. 如果创建多个,那两个窗口的内容是否一致?
  3. 如果不一致,会产生误导,如果一致,以哪个为标准?

这种场景就没有必要创建多个类的实例化对象

如何实现单例模式?

要点

  1. 构造、拷贝构造、析构私有化,防止类外直接创建对象并初始化
  2. 通过静态指针获取对象实例,使用指针的原因是指针在32位平台下固定占4字节与类型无关,如果使用对象,类中成员变量较多时占用空间很大
  3. 提供公有的获取对象实例的方法,并且不能依赖对象调用,所以需要使用静态函数,通过类名+作用域直接调用
  4. 提供手动释放对象空间的方法,防止类内其它申请在堆区空间的成员产生内存泄露

饿汉式

类产生的时候就创建好实例对象,用空间换时间,实现简单,不用考虑线程安全的问题

上代码

#include<iostream>
using namespace std;

//饿汉
class Singleton
{
private:
	Singleton(){cout<<"Singleton::Singleton()"<<endl;}
	Singleton(const Singleton&);
	Singleton& operator=(const Singleton&);
	~Singleton(){cout<<"Singleton::~Singleton()"<<endl;}
    //实例化对象指针
    static Singleton* m_instance;
public:
	static Singleton* GetInstance()
	{
		if(m_instance == nullptr)
			m_instance = new Singleton;
		return m_instance;
	}
    //手动释放空间的函数
	static void DestroyInstance()
	{
		if(m_instance != nullptr)
		{
			delete m_instance;
			m_instance = nullptr;
		}
	}

};

Singleton* Singleton::m_instance = new Singleton();

int main()
{
	system("pause");
	return 0;
}

输出结果

如我们所想的一样,饿汉式单例模式加载程序时,类已经创建好实例对象,无需调用


修改main函数中的代码

int main()
{
    Singleton* m_instance1 = Singleton::GetInstance();
	Singleton* m_instance2 = Singleton::GetInstance();
	system("pause");
	return 0;
}

输出结果

多次获取对象时,类中只有一个实例对象


调用释放空间函数

int main()
{
    Singleton* m_instance1 = Singleton::GetInstance();
	Singleton* m_instance2 = Singleton::GetInstance();
    Singleton::DestroyInstance();
	system("pause");
	return 0;
}

能够正确调用析构函数,释放内存空间


懒汉式

调用获取对象实例的函数才创建对象实例,即按需创建,适用于对资源要求十分严格的场景

#include<iostream>
using namespace std;

class CSingleton
{
private:
	CSingleton(){cout<<"普通构造:CSingleton::CSingleton()"<<endl;}
	~CSingleton(){cout<<"析构函数:CSingleton::~CSingleton()"<<endl;}
	CSingleton(const CSingleton& single){cout<<"拷贝构造:CSingleton::CSingleton(CSingleton& single)"<<endl;}
	CSingleton& operator = (const CSingleton& single){cout<<"=赋值运算符:CSingleton::operator=()"<<endl;}
	static CSingleton* m_pInstance;
public:
	static CSingleton* GetInstance()
	{
		if(!m_pInstance)
		{
			m_pInstance = new CSingleton;
		}
		return m_pInstance;
	}
};

CSingleton* CSingleton::m_pInstance = nullptr;

int main()
{
	system("pause");
    return 0;
}

输出结果

可以看出,与饿汉式不同,懒汉式初始化时将静态指针赋值为空,并没有在程序运行加载类时实例化对象 


修改main函数

int main()
{
	CSingleton* single = CSingleton::GetInstance(); 
	CSingleton* single2 = CSingleton::GetInstance();
	if(single == single2)
	{
		cout<<&(*single)<<"  "<<&(*single2)<<endl;
		cout<<"the same ptr"<<endl;
	}
	system("pause");
    return 0;
}

输出结果

  

两次调用GetInstance方法,两个实例化对象指针指向同一个地址空间


线程安全问题

#include<iostream>
#include<mutex>
#include<thread>
using namespace std;

class CSingleton
{
private:
	CSingleton(){cout<<"普通构造:CSingleton::CSingleton()"<<endl;}
	~CSingleton(){cout<<"析构函数:CSingleton::~CSingleton()"<<endl;}
	CSingleton(const CSingleton& single){cout<<"拷贝构造:CSingleton::CSingleton(CSingleton& single)"<<endl;}
	CSingleton& operator = (const CSingleton& single){cout<<"=赋值运算符:CSingleton::operator=()"<<endl;}
	static CSingleton* m_pInstance;
public:
	static CSingleton* GetInstance()
	{
		if(!m_pInstance)
		{
			m_pInstance = new CSingleton;
		}
		return m_pInstance;
	}
};

CSingleton* CSingleton::m_pInstance = nullptr;

void f1()
{
	CSingleton* single1 = CSingleton::GetInstance();
}
void f2()
{
	CSingleton* single2 = CSingleton::GetInstance();
}
int main()
{
	std::thread t1(f1);
	std::thread t2(f2);
	t2.join();
	t1.join();
	system("pause");
    return 0;
}

 输出结果

与饿汉式不同的是,我们的实例化对象并不是在加载类时直接创建的,而是通过手动调用GetInstance函数创建

在多线程环境下,假设单例还未初始化,有两个线程同时调用GetInstance方法,然后两个线程都初始化一个单例,调用了两次构造函数

不符合单例的条件,所以懒汉式的写法会出现线程安全的问题!


加锁

#include<iostream>
#include<mutex>
#include<thread>
using namespace std;

std::mutex mtx;	
class CSingleton
{
private:
	CSingleton(){cout<<"普通构造:CSingleton::CSingleton()"<<endl;}
	~CSingleton(){cout<<"析构函数:CSingleton::~CSingleton()"<<endl;}
	CSingleton(const CSingleton& single){cout<<"拷贝构造:CSingleton::CSingleton(CSingleton& single)"<<endl;}
	CSingleton& operator = (const CSingleton& single){cout<<"=赋值运算符:CSingleton::operator=()"<<endl;}
	static CSingleton* m_pInstance;
public:
	static CSingleton* GetInstance()
	{
		mtx.lock();
		if(!m_pInstance)
		{
			m_pInstance = new CSingleton;
		}
		mtx.unlock();
		return m_pInstance;
	}
};

CSingleton* CSingleton::m_pInstance = nullptr;

void f1()
{
	CSingleton* single1 = CSingleton::GetInstance();
}
void f2()
{
	CSingleton* single2 = CSingleton::GetInstance();
}
int main()
{
	std::thread t1(f1);
	std::thread t2(f2);
	t2.join();
	t1.join();
	system("pause");
    return 0;
}

输出结果

使用互斥锁加锁后,同一时间只有一个线程能获得锁,进行实例化对象,解决了线程安全问题

但还是存在一些问题,每次调用GetInstance()时,都需要加锁,会产生一定的开销

双检锁

static CSingleton* GetInstance()
{
    if(!m_pInstance)
	{
		mtx.lock();
		if(!m_pInstance)
		{
			m_pInstance = new CSingleton;
		}
		mtx.unlock();
	}
		
	return m_pInstance;
}

第一次判断,如果加锁前就有实例,就不需要再加锁进入了

第二次判断,如果线程进入时发现已经有实例了,直接退出不能再实例化一个对象

双检锁的操作,每次调用GetInstance(),先判断实例化对象是否存在,如果存在就不用加锁而是直接返回对象,减少了加锁的开销

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值