单例模式

单例模式

单例模式在日常开发的时候用的还是比较多的,写法呢也不较多,但是了很多时候可能写的不够完善,比如内存没有进行正确释放,没有进行加锁操作。前者可能出现段错误和内存泄漏,后者可能出现多线程竞争的问题。
不多BB直接上代码


class singleton {
public:
	static singleton* GetInstance() {
		if (_instance == nullptr)
		{
			_instance = new singleton();
		}

		return _instance;
	}

	singleton(const singleton&) = delete;
	singleton& operator = (const singleton&) = delete;
private:

	singleton() {		
	}

	~singleton() {
	 if (_instance != nullptr)
		delete _instance;

		_instance = nullptr;
	}
	
	static singleton* _instance;
};
singleton* singleton::_instance = nullptr; //类外进行初始化

很多人写出来的可能就是上面这样子的,有点基础的人肯定就知道,多线程的时候会出现竞争的问题,可能new的时候会new多份,导致内存泄漏,重复加载的问题。
其实问题不止这些 ,因为在创建对象的时候对象在堆上,你拿到的只是一个它的指针,指针在退栈的时候时候不会自动释放的,你去delete这个指针也不行,因为析构函数是私有的。只能说在程序退出的时候由系统给你释放,而操作系统怎么释放我们是不知道的,就有可能导致崩溃。

class singleton {
public:
	static singleton* GetInstance() {
		if (_instance == nullptr)
		{
			_instance = new singleton();
			atexit(Destructor);   /*由于对象在堆上, 栈上只有一个指针,指针在退栈的时候不会释放内存*/
		}

		return _instance;
	}

	singleton(const singleton&) = delete;
	singleton& operator = (const singleton&) = delete;
private:
	static void Destructor() { 
		if (_instance != nullptr)
			delete _instance;

		_instance = nullptr;
	}

	singleton() {		
	}

	~singleton() {
		
	}
	static singleton* _instance;
};
singleton* singleton::_instance = nullptr; //类外进行初始化

上面加了一个Destuctor函数,并且用atexit()将其绑定进程退出时调用,上述方式解决了内存释放的问题。
释放内存的方式也不止上面的这么一种,还有内部类,智能指针等等。但是多线程竞争的问题并没有解决。

其实上面这个玩意也能用,笔者在之前的一个项目中,就用的这种方式,基本上没出现啥问题。用我师傅的话说: 一下就过去了,不用管那么多。

下面是加锁版本

class singleton {
public:
	static singleton* GetInstance() {
	//	std::lock_guard<std::mutex> lock1(_mutex1);  //第一处
		if (_instance == nullptr)
		{
	//		std::lock_guard<std::mutex> lock2(_mutex2); //第二处
			if (_instance == nullptr)
			{
				_instance = new singleton();
			}
		}
		return _instance;
	}

	singleton(const singleton&) = delete;
	singleton& operator = (const singleton&) = delete;
private:

	singleton() {	
		std::cout << "构造函数被调用" << endl;
	}

	~singleton() {
		
	}
	static singleton* _instance;

	static std::mutex _mutex1;
	static std::mutex _mutex2;
};
singleton* singleton::_instance = nullptr; //类外进行初始化
std::mutex singleton::_mutex1; //类外初始化
std::mutex singleton::_mutex2;

看不懂这个锁的点这里

上面的内容一共有两个位置可以加锁,如果在第一处加锁,那么每次取资源都需要加锁,挂起等待的线程会进行线程的切换,资源消耗很大。

第二处加锁 :放开的第二处的代码,双检测的目的是为了提高效率。但是把还是可能存在问题,在执行new的时候,一共三个动作,分配内存,调用构造函数,赋值。由于现在cpu都是流水线模式一次性取多个指令,可能进行指令重排。比如说先进行了开内存和赋值,但是没有调用构造函数,这时候另一个线程看_instance已经有值了,就直接返回了,而这段空间还没有被构造函数初始化,那么就有可能出现问题。出现这个问题的概率我感觉很小很小很小。 比较CPU的频率在哪里摆着,不过肯定还有更优雅的方法。

C++11 中出现了一个特性,static变量在初始化的时候,并发同时进入声明语句,并发线程将会组设等待初始化结束。

class singleton1 {
	static singleton1& GetInstance()
	{
		static singleton1 instance;
		return instance;
	}

private:
	singleton1(const singleton1&);
	singleton1(){}
	singleton1& operator = (const singleton& ){}

};

这样也是一个比较不错的方法

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值