C++单例类最好的写法(支持多线程)

1.单例类

某个类只能有一个对象,即为单例

class A
{
}
...
A a1;
A a2;//这就不是单例,不能有第二个对象

下面是一个常用的C++单例类的形式:

class S
{
private:
	S() {}   //私有构造函数,单例类不能通过普通放方式构造对象
private:
	static S* m_Instance;  //指向该类唯一的对象的指针
public:
	static S* GetInstance() {//注意这是个静态成员函数
		if (m_Instance == NULL) {//如果没有对象,则构造对象
			m_Instance = new S;
			static ToDestroy destroyer;//声明一个静态变量,程序结束时释放,调用其析构函数,顺便释放m_Instance
		}
		return m_Instance;
	}
	class ToDestroy//嵌套类,用于释放。其实不释放系统也会自动回收
	{
	public:
		~ToDestroy(){
			if (S::m_Instance){
				delete S::m_Instance;
				S::m_Instance = NULL;
			}
		}
	};
};
Single* Single::m_Instance = NULL;//一定要记得为类的静态成员变量赋值!!!

这个类的特点:
1.构造函数是private的
2.有一个静态指针,即该类唯一的对象的地址,同样是private
3.使用静态GetInstance接口来获取唯一对象的地址,若地址为空则创建对象并返回地址,若地址非空则直接返回地址
4.这里使用一个嵌套类来释放资源,可有可无
5.记得要为类的静态成员变量赋值!!!

2.单例类在多线程中的问题

1.以上单例类创建对象的过程一定要在出现多线程之前完成
先把单例类对象创建好,里面的数据都设计好,在后面分多线程的时候只读数据就不会有问题

2.如果一定要在分出多线程后再去调用GetInstance()创建单例类的对象就会出现问题。
我们需要加一个锁来解决这个问题

include<thread>//C++11
include<mutex>

mutex _Mutex;
class S
{
private:
	S() {}   //私有构造函数,单例类不能通过普通放方式构造对象
private:
	static S* m_Instance;  //指向该类唯一的对象的指针
	
public:
	static S* GetInstance() {//注意这是个静态成员函数
		if (m_Instance == NULL) {//双重锁定,用于提高效率,否则每次都要加锁
			lock_guard<mutex> guard(_Mutex);//加锁,自动释放
			if (m_Instance == NULL) {
				m_Instance = new S;
				static ToDestroy destroyer;//声明一个静态变量,程序结束时释放,调用其析构函数,顺便释放m_Instance
			}
		}
		return m_Instance;
	}
	class ToDestroy//嵌套类,用于释放。其实不释放系统也会自动回收
	{
	public:
		~ToDestroy(){
			if (S::m_Instance){
				delete S::m_Instance;
				S::m_Instance = NULL;
			}
		}
	};
};
Single* Single::m_Instance = NULL;//一定要记得为类的静态成员变量赋值!!!

int main()
{
	auto fun = [] {
		Single::GetInstance()->Run();
	};
	thread tobj1(fun);
	thread tobj2(fun);
	tobj1.join();
	tobj2.join();
	return 0;
}

这个类的特点:
1.mutex一定不能是类的成员
2.使用双重锁定提高效率
3.互斥量加锁的位置是在第二个判断m_Intance是否存在的前面
4.这里使用一个嵌套类来释放资源,可有可无
那么为什么不能这样写:

if (m_Instance == NULL) {//双重锁定,用于提高效率,否则每次都要加锁
			lock_guard<mutex> guard(_Mutex);//加锁,自动释放
			m_Instance = new S;
			static ToDestroy destroyer;
		}

即不要第二个判断。要注意,第一次运行时,如果在

if (m_Instance == NULL)

后切换了上下文,则多个线程都进入到了判断体中,一个线程锁住先进行了new语句,解锁后其他进入判断体内的线程都会在挨个new一次,这显然是不对的。
所以,在锁上之后,一定得判断一下。
而在所之前也得判断一下,否则每次调用都得锁。

3.使用call_once()进一步加速

call_once()的第二个参数是一个函数名fun
call_once()的功能是可以保证这个函数fun()只会被调用一次
call_once()具有互斥量的功能,但消耗的资源更少。
call_once()需要与标记std::once_flag结合使用。

#include<iostream>
#include<thread>
#include <deque>
#include<mutex>

#include <windows.h>
mutex mMutex;
std::once_flag mFlag;
class Single
{
private:
	Single() {}   //私有构造函数,单例类不能通过普通放方式构造对象
	static Single* m_Instance;  //指向该类唯一的对象的指针
	static void CreateInstance() {
		m_Instance = new Single;
		static ToDestroy destroyer;
	}
public:
	
	static Single* GetInstance() {
		call_once(mFlag, CreateInstance);//28985ms
		//if (m_Instance == NULL) {//双重锁定,用于提高效率,否则每次都要加锁
		//	lock_guard<mutex> guard(mMutex);//加锁,自动释放
		//	if (m_Instance == NULL) {
		//		m_Instance = new Single;
		//		static ToDestroy destroyer;//声明一个静态变量,程序结束时释放,调用其析构函数,顺便释放m_Instance
		//	}
		//}//32203ms
		return m_Instance;
	}
	class ToDestroy//嵌套类,用于释放。其实不释放系统也会自动回收
	{
	public:
		~ToDestroy(){
			if (Single::m_Instance){
				delete Single::m_Instance;
				Single::m_Instance = NULL;
			}
		}
	};
	void Run() { cout << "good" << endl; }
};
Single* Single::m_Instance = NULL;//一定要记得为类的静态成员变量赋值!!!
int main()
{
	auto fun = [] {
		for (int i = 0; i < 10000; i++)
			Single::GetInstance()->Run();
	};
	DWORD timeSpent = 0;
	DWORD beginTime = GetTickCount();
	/*thread tobj1(fun);
	thread tobj2(fun);
	tobj1.join();
	tobj2.join();*/
	for (int i = 0; i < 100000; i++)
		Single::GetInstance()->Run();
	DWORD endTime = GetTickCount();
	timeSpent = endTime - beginTime;
	cout << "耗时:" << timeSpent << "ms" << endl;
	system("pause");
	return 0;
}

在release x86环境中测试发现
使用

call_once(mFlag, CreateInstance);

耗时28985ms

而使用

if (m_Instance == NULL) {
	lock_guard<mutex> guard(m_Mutex);
	if (m_Instance == NULL) {
		m_Instance = new Single;
		static ToDestroy destroyer;
	}
}

则需要32203ms

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值