C++实现单例模式-多种方式比较

说明

单例模式,面试中经常被问到,但是很多人只会最简单的单例模型,可能连多线程都没考虑到,本文章从最简单的单例,到认为是最佳的单例模式实现方式,单例模式没有什么知识点,直接上源码

源代码

版本一

这个版本是最简单的版本,但是存在2个问题:(1)不支持多线程(2)需要主动调用函数来释放对象,否则程序结束后,不会调用析构函数,这可能会造成严重的后果,比如单例类对象需要在释放时做一些网络数据传输的工作,最后释放连接,在关闭连接前,会做一些判断,是否可以释放连接,可以释放连接时,才去释放连接,这些都可以在析构函数里完成

//缺点:
//1.需要主动去释放单例对象,否则会造成内存泄漏
//2.不支持多线程
class Singleton
{
public:
	static Singleton* GetInstance() {
		if (_instance == nullptr)
		{
			_instance = new Singleton();
		}
		return _instance;
	}

	//
	static void freeInstance()
	{
		if (_instance != nullptr) {
			delete _instance;
		}
		_instance = nullptr;
	}
private:
	Singleton() { std::cout << "new Singleton" << std::endl; }
	~Singleton() { std::cout << "~Singleton" << std::endl; }//
	Singleton(const Singleton& clone) = delete;
	Singleton& operator=(const Singleton&) = delete;
	//存放在静态区,程序结束,生命周期结束,只是将指针设置为nullptr,并没有去调用析构函数,如果想被释放,
	//必须加一个函数去主动释放,那么通常就需要将释放函数放到程序结束的位置(否则单例就没有意义),如果一个系统中需要使用多个单例
	//会调用释放函数,不方便
	static Singleton* _instance;
};
Singleton* Singleton::_instance = nullptr;//静态变量必须初始化

版本2

版本1需要主动去释放对象不能主动释放对象的空间,有了版本2

//版本1需要主动释放单例,不太方便,因此这里解决主动释放的问题,使用atexit:在程序结束后自动去调用析构函数,释放资源
class Singleton2 {
public :
	static Singleton2* GetInstance()
	{
		if (_singleton == nullptr)
		{
			_singleton = new Singleton2();
			atexit(destructor);//释放操作,不会造成内存泄漏或者资源没有被释放的问题
		}
		return _singleton;
	}
private:
	static void destructor() {
		if (nullptr != _singleton)
		{
			delete _singleton;
		}
		_singleton = nullptr;
	}
	Singleton2() { std::cout << "new Singleton2" << std::endl; }
	~Singleton2() { std::cout << "~Singleton2" << std::endl; }
	Singleton2(const Singleton2& cpy) = delete;
	Singleton2& operator=(const Singleton2& other) = delete;
	static Singleton2* _singleton;
};
Singleton2* Singleton2::_singleton = nullptr;//静态变量必须初始化

版本3

解决版本1的2个问题,但是这个版本有一个隐含的知识点,很多人不知道这个知识点,就很难发现这个版本的问题所在

//这个例子好像没什么问题,一般人也很难发现,但是这个如果上线,会出现莫名其妙的错误,主要原因在new实例的时候说明
class Singleton3 {
public:
	static Singleton3* GetInstance()
	{
		//std::lock_guard lock(mutex);//枷锁不要加在这里,这里锁的范围太大了,单例模式,主要是读取
		//如果在这里枷锁,基本每次调GetInstance都会枷锁,性能不高。
		if (instance == nullptr)
		{
			std::lock_guard<std::mutex> lock(mutex);
			//为什么需要双重判断,有可能个进程同时进入到外层if(instance == nullptr),这时候一个线程进入到这里
			//另一个线程会卡住,当第一个线程释放时,如果不进行null判断,会造成构造2次
			if (instance == nullptr)
			{
				//下一句主要分为3个步骤
				//1.分配空间malloc
				//2.构造函数初始化
				//3.赋值给instance
				//通常情况下,cpu为了效率,可能会出现1,2,3,也可能会出现1,3,2
				//当执行顺序为1,3,2时,当这个线程执行完1,3步骤时,假如另一个线程恰好执行到外层if,那么由于已经赋值
				//但是赋值却不是初始化的值,其他线程去执行的时候拿到的就是错误的数据,这就是多线程会导致的问题。
				//可以使用内存屏障解决,这里就不讲了,请自行参考其他文章
				instance = new Singleton3();// 
				atexit(deconstractor);
			}
		}
		return instance;
	}
private:
	static void deconstractor() {
		if (instance != nullptr)
		{
			delete instance;
		}
		instance = nullptr;
	}
	Singleton3() { std::cout << "new Singleton3" << std::endl; }
   ~Singleton3() { std::cout << "~Singleton3" << std::endl; }
   Singleton3(const Singleton3& cpy) = delete;
   Singleton3& operator=(const Singleton3& other) = delete;
   static Singleton3* instance;
   static std::mutex mutex;
};
Singleton3* Singleton3::instance = nullptr;
std::mutex Singleton3::mutex;

版本4

解决CPU reorder的问题,比较好的解决方案

//局部静态对象,生命周期结束后会自动释放空间,
//而且static也是线程安全的,假如2个线程同时调用GetInstance,不会创建2个instance
class Singleton5 {
public:
	static Singleton5& GetInstance() {
		static Singleton5 instance;
		return instance;
	}
private:
	Singleton5() { std::cout << "new Singleton5" << std::endl; }
	~Singleton5() { std::cout << "~Singleton5" << std::endl; }
	Singleton5(const Singleton5&) = delete;
	Singleton5& operator=(const Singleton5& other) = delete;
};

版本6

版本5 不利于扩展,使用模板来解决所有类的单例模式

//上面一个版本已经很好了,但是扩展性差,只能用于一个类的单例,使用模板来实现任何类的单例
template <typename T>
class Singleton6{
public:
	static T& GetInstance() { //T是子类,返回子类对象的引用
		static T instance;
		return instance;
	}

protected://子类可访问父类的构造函数,初始化和释放对象时,都会调用父类的构造函数
	Singleton6() { std::cout << "new Model Singleton6" << std::endl; }
	~Singleton6() { std::cout << "~Model Singleton6" << std::endl; }
	Singleton6(const Singleton6&) = delete;
	Singleton6& operator=(const Singleton6& other) = delete;
};

//注意,父类为模板类Singleton6,子类为DesignPattern,将子类传递到父类
class DesignPattern :public Singleton6<DesignPattern>
{
 //由于对象是在父类对象构造的,而我们将构造和析构设置为private,父类要想访问,就需要将其设置为友元类
 friend	Singleton6<DesignPattern>;
private:
	DesignPattern() { std::cout << "new DesignedPattern" << std::endl; }
	~DesignPattern() { std::cout << "~DesignPattern" << std::endl; }
	DesignPattern(const DesignPattern& cpy) = delete;
	DesignPattern& operator=(const DesignPattern& other) = delete;
};

你可能感兴趣的:(C/C++编程,零声-linux课程总结,单例模式,c++,设计模式)