C++Singleton设计模式思考

先说重点,我觉得网上很多文章的Singleton实现都有些老。
我认为只需要这么写。

class Singleton1
{
	Singleton1()
	{
	}
public:
	static Singleton1& get_instance()
	{
		static Singleton1 instance;
		return instance;
	}
};

Singleton模式的核心特点就是把构造函数设为私有。这样就能保证没有实例能被创建。那如何仅创建一个实例呢?在类内(类外其实也行)写个get_instance()函数,这个函数必须是静态的(类外不必),因为它在类内,类内只有静态的(成员函数或成员变量)才会被类所共享,什么叫共享?你用这个类创建多个实例,每个实例的成员变量或者成员函数都是属于自己实例的。再简单点举个例子 :

class A
{
public:
	void b()
	{
		cout << "This is b function";
	}
	static void c()
	{
		cout << "This is c function";
	}
};
int main()
{
	A::b();//出现Error,它属于一个实例
	A::c();//right,它属于整个类
	A a;
	a.b();//right
}

举的是成员函数的例子,成员变量也是同理。那么继续说,静态get_instance()方法中创建一个静态实例,静态实例保证这个实例永远只有一个。那到底是如何保证的呢?这就需要涉及内存分配了(详见这篇博客)。get_instance()方法返回引用,在能使用引用的情况下尽量避免使用指针,引用与指针最大的区别在于引用不能为空,一个引用必然有它引用的对象,但一个指针可以为空,空指针总会给人不好的联想,但在这个例子中好像并无大碍,第二大区别就是引用声明的同时就必须得定义,并且它将不能更改引用其他的对象。(这两大特点其实很好理解,因为创建一个引用实际上就是为一个对象创建一个别名。)
这两个区别好像都不足以说服你在实现Singleton中引用比指针好在哪里,那你在想想如果用指针来实现,大概应该是这样吧

class Singleton2
{
	static Singleton2 *p;
	Singleton2()
	{

	}
public:
	static Singleton2* get_instance()
	{
		if (p == nullptr)
		{
			p = new Singleton2();
		}
		return p;
	}
};

你或许突然想到了问题所在:光从freestore中new了内存,没有delete掉啊?。这就需要再写个Destroy方法,在其中delete。然后在真正使用这个Singleton实例时,使用后最后还得调用个Destroy,忘记调用了就会导致内存泄漏。这就是真正的麻烦所在。而且new还是从自由存储区上分配的,比从栈上分配速度慢,何苦用哉。那为什么还是有的文章中这么用了呢(据说好像设计模式书里也是这么用的)。我想原因或许是考虑到多线程了吧。在多线程并发条件下,各个线程都会认为自己是第一个遇见static的“人”,并争抢着初始化,会导致data race。所以你就不能在get_instance方法中靠构造函数直接static一个实例,而是需要保护初始化过程。

有的文章中保护的方式还用到了声名狼藉的double check lock(《C++并发编程实战》)

双重检验锁是一种不好的方式,其实这些我也都是从这本书上才知道的。它潜在着一种竞争关系详见这篇文章。跳过所有细节,我要说的重点就是

C++11以后的编译器在多线程的情况下使用static不会产生race condition。

初始化及定义完全在一个线程中发生,并且没有其他线程可在初始化完成前对其进行处理。《C++并发编程实战》

也就是说根本不需要保护数据的初始化过程,按我最初给的代码那么写就行。如果以后真的需要用到相同的Singleton但是需要多种不同的类型,填个template就完了,要是还需要继承(真的会需要继承么)就把构造函数放protected里就完了。

发布了26 篇原创文章 · 获赞 6 · 访问量 6465

猜你喜欢

转载自blog.csdn.net/weixin_43975128/article/details/97502079