三种单例模式的C++实现

  对于c++实现的单例模式,在多线程环境中,存在的不可靠及其解决办法,由于能力限制,还没有完全摸索清楚,在此暂记。

一 简介

  因为在设计或开发中,肯定会有这么一种情况,一个类只能有一个对象被创建,如果有多个对象的话,可能会导致状态的混乱和不一致。这种情况下,单例模式是最恰当的解决办法。它有很多种实现方式,各自的特性不相同,使用的情形也不相同。今天要实现的是常用的三种,分别是饿汉式、懒汉式和多线程式。
  通过单例模式,可以做到:
  1、确保一个类只有一个实例被建立;
  2、提供了一个对对象的全局访问指针;
  3、在不影响单例类的客户端的情况下允许将来有多个实例。

二 懒汉式

  懒汉式:就是说当你第一次使用时才创建一个唯一的实例对象,从而实现延迟加载的效果。因为此时可能存在多线程竞态环境,如不加锁限制会导致重复构造或构造不完全问题。

class CSingleton{
public:
	static CSingleton *GetInstance()
	{
		if (m_pInstance == NULL)
		{
			m_pInstance = new CSingleton();
		}
		return m_pInstance;
	}
private:
	CSingleton(){};
	static CSingleton *m_pInstance;
};

CSingleton *CSingleton::m_pInstance = nullptr;

  GetInstance()使用懒惰初始化,也就是说它的返回值是当这个函数首次被访问时被创建的。这是一种防弹设计——所有GetInstance()之后的调用都返回相同实例的指针:

CSingleton *p1 = CSingleton::GetInstance();
CSingleton *p2 = p1->GetInstance();
CSingleton &ref = *CSingleton::GetInstance();

  GetInstance稍加修改,这个设计模板便可以适用于可变多实例情况,如一个类允许最多五个实例。
  代码很简单,但是会存在内存泄漏的问题,new出来的东西始终没有释放,下面是一种饿汉式的一种改进。

class CSingleton{
private:
	CSingleton(){}
	static CSingleton *m_pInstance;
	class CGarbo{
	public:
		~CGarbo()
		{
			if(CSingleton::m_pInstance)
			{
				delete CSingleton::m_pInstance;
			}
		}
	};
	static CGarbo Garbo;
public:
	static CSingleton * GetInstance()
	{
		if(m_pInstance == NULL)
		{
			m_pInstance = new CSingleton();
		}
		return m_pInstance;
	}
};

CSingleton *CSingleton::m_pInstance = nullptr;

  在程序运行结束时,系统会调用CSingleton的静态成员Garbo的析构函数,该析构函数会删除单例的唯一实例。使用这种方法释放单例对象有以下特征:
  1、在单例类内部定义专有的嵌套类。
  2、在单例类内定义私有的专门用于释放的静态成员。
  3、利用程序在结束时析构全局变量的特性,选择最终的释放时机。
  以上代码是非线程安全的,多线程环境需要加入同步机制:

class Singleton{
public:
	//获取唯一实例的接口函数
	static Singleton* GetInstance()
	{
		//双重检查,提高效率,避免高并发场景下每次获取实例对象都进行加锁
		if (_sInstance == NULL)
		{
			std::lock_guard<std::mutex> lock(_mtx);
			if (_sInstance == NULL)
			{
				Singleton* tmp = new Singleton;
				MemoryBarrier(); //内存栅栏,防止编译器优化
				_sInstance = tmp;
			}
		}		
		return  _sInstance;
	}
	static void DelInstance()
	{
		if (_sInstance)
		{
			delete _sInstance;
			_sInstance = NULL;
		}
	}
	void Print()
	{
		std::cout << _data << std::endl;
	}
private:
	//构造函数定义为私有,限制只能在类内实例化对象
	Singleton():_data(10){}
	//防拷贝
	Singleton(const Singleton&);
	Singleton& operator=(const Singleton&);
private:
	static std::mutex _mtx; //保证线程安全的互斥锁
	static Singleton *_sInstance; //指向实例的指针定义为静态私有,这样定义静态成员获取对象实例
	int _data; //单例类里面的数据
};

Singleton *Singleton::_sInstance = nullptr;

三 饿汉式

  饿汉式的特点是一开始就加载了,如果说懒汉式是“时间换空间”,那么饿汉式就是“空间换时间”,因为一开始就创建了实例,所以每次用到的之后直接返回就好了。

class CSingleton{
private:
	CSingleton(){};
public:
	static CSingleton *GetInstance()
	{
		static CSingleton m_pInstance;
		return &m_pInstance;
	}
};

  饿汉式是线程安全的,在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变。
  在这篇文章中,指出了上面饿汉模式代码存在的线程安全问题,原因是:静态的局部变量是在调用的时候分配到静态存储区,所以在编译的时候没有分配。以下为线程安全的代码:

class CMsBsGPSInfoStart{
public:
	static CMsBsGPSInfoStart &GetInstance();
protected:
 	CMsBsGPSInfoStart();
	~CMsBsGPSInfoStart();
private:
	static CMsBsGPSInfoStart _instance;
private:
	//CLock m_lkMsBsGPSInfoStartFlag;
	bool m_bMsBsGPSInfoStartFlag;    //
 public:
	bool GetMsBsGPSInfoStart();
	bool SetMsBsGPSInfoStart(bool bIsStart);
};

CMsBsGPSInfoStart CMsBsGPSInfoStart::_instance;

CMsBsGPSInfoStart::CMsBsGPSInfoStart():m_bMsBsGPSInfoStartFlag(false)
{
	std::cout << "enter CMsBsGPSInfoStart::CMsBsGPSInfoStart()" << endl;
}
 
CMsBsGPSInfoStart::~CMsBsGPSInfoStart()
{
	std::cout << "enter CMsBsGPSInfoStart::~CMsBsGPSInfoStart()" << endl;
}
 
CMsBsGPSInfoStart &CMsBsGPSInfoStart::GetInstance()
{
	std::cout << "CMsBsGPSInfoStart::GetInstance()" << endl;
	return _instance;
}

bool CMsBsGPSInfoStart::SetMsBsGPSInfoStart(bool bIsStart)
{
	m_bMsBsGPSInfoStartFlag = bIsStart;
	return true;
}

int main()
{
	return 0;
}

  测试结果如下:
在这里插入图片描述

四 多线程下的单例模式

  在懒汉式的单例类中,其实有两个状态,单例未初始化和单例已经初始化。假设单例还未初始化,有两个线程同时调用GetInstance方法,这时执行m_pInstance == NULL肯定为真,然后两个线程都初始化一个单例,最后得到的指针并不是指向同一个地方,不满足单例类的定义了,所以懒汉式的写法会出现线程安全的问题!在多线程环境下,要对其进行修改。
  这里要处理的是懒汉模式。

class CSingleton{
private:
	static CSingleton* m_pInstance;
	CSingleton(){}
public:
	static CSingleton* GetInstance();
};

CSingleton* CSingleton::GetInstance()
{
	if(NULL == m_pInstance)
	{
		Lock();//借用其它类来实现,如boost
		if(NULL == m_pInstance)
		{
			m_pInstance= new CSingleton;
		}
		UnLock();
	}
	return m_pInstance;
}

CSingleton *CSingleton::m_pInstance = nullptr;

  使用double-check来保证线程安全。但是如果处理大量数据时,该锁才成为严重的性能瓶颈。
  注:其实以上的加锁方式是不可靠的。参考:
https://blog.csdn.net/nodeathphoenix/article/details/51657973

https://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
https://silviuardelean.ro/2012/06/05/few-singleton-approaches/

你可能感兴趣的:(Software,Engineering)