C++ 单例模式详解

C++ 单例模式详解

一、单例模式:

什么是单例:在面向对象编程中,我们创建对象的过程就是创建一个类实例,一个类是可以创造很多的类实例的,所谓单例就是规定一个类就只能创建出一个实例对象,比如:打印机实例,只要有一个就行。

一些使用场景:

  • 比如我们在使用数据缓冲区的时候,使用一个缓冲区对象,那么这时就应该往一个地方写入数据或读取数据;
  • 比如一个打印机,很多人使用,但是我们只需要一个就可以了,所以只要一个打印机对象。
  • 总之是这种多对一关系的,这个一就可以考虑使用单例来实现。
二、单例的实现思路

在c++中,创建一个对象,首先会先调用类的构造函数,那么,实现单例就是要把构造函数私有化,然后给外界提供一个接口用来返回这个类的实例对象,现在只要让接口每一次返回的都是同一个实例对象就行了,那么这个实例对象就应该被创建为一个静态对象,保存静态数据区。

##### 一、懒汉模式1.0
#include<iostream>


class Singleton
{
public:
		//禁用拷贝构造和赋值构造函数
		Singleton(Singleton& s) = delete;
		Singleton& operator=(const Singleton&	s) = delete;	
		~Singleton(){}	


		static	Singleton* getInstance();//声明接口为静态函数,通过类名访问
private:

	Singleton(){std::cout<<"construct start"<<std::endl;}

	static Singleton* instance_;//声明单例为静态变量	

};

//类外定义
Singleton* Singleton::instance_ = nullptr;			

//类外实现
Singleton* Singleton::getInstance()
{
	if(instance_ == nullptr)
	{
		instance_ =  new Singleton();
	}
	
	return instance_;	
	
}	

int main()
{
	Singleton* s1 = Singleton::getInstance();
	Singleton* s2 = Singleton	:: getInstance();				
}	

执行结果:construct start

这个是最简单的单例模式,但是我们思考一下:如果有两个线程的时候,它们都抢着来调用getInstance这个函数,同时进入然后new了两次也是有可能的,也就说我们的单例并没有考虑线程安全,还有既然这个是一个指针,那么我们就应该考虑释放,我们并没有释放可能造成内存泄露,所以这时候应该使用智能指针管理。

二、懒汉模式2.0
#include<iostream>
#include<mutex>
#include<memory>


class Singleton
{
public:
		//禁用拷贝构造和赋值构造函数
		Singleton(Singleton& s) = delete;
		Singleton& operator=(const Singleton&	s) = delete;	
		~Singleton(){std::cout<<"destruct start"<<std::endl;}	

		typedef std::shared_ptr<Singleton> Ptr;
		static	Ptr getInstance();//声明接口为静态函数,通过类名访问
private:

	Singleton(){std::cout<<"construct start"<<std::endl;}

	static Ptr 	instance_;
	static std::mutex m_mutex;//声明互斥量
};

//类外定义
Singleton::Ptr	Singleton::instance_ = nullptr;
std::mutex Singleton::m_mutex;	

//类外实现
Singleton::Ptr Singleton::getInstance()
{
	if(instance_ == nullptr)
	{
		//在new之前先进行锁操作
		std::lock_guard<std::mutex> lk(m_mutex);//用类模板定义的锁对象在大括号中后就释放了,所以不用解锁
		if(instance_ == nullptr)//由于加锁的时候可能会有别的线程先抢了锁并且执行了new,所以这里需要doublecheck
		{
			instance_ =  Ptr (new Singleton());

		}
	}
	
	return instance_;	
	
}	

int main()
{
	Singleton::Ptr s1 = Singleton::getInstance();
	Singleton::Ptr s2 = Singleton::getInstance();				
}	

执行结果:construct start
destruct start

由于用智能指针管理堆内存,所以会自动释放,线程安全且内存安全。

但是这种做法要求必须使用智能指针,而且有的时候锁会因为一些原因出问题,可能提的有点苛刻,但是真的可能有问题,而且这种写法代码比较复杂,其实还有一种超级简单并且安全的写法,就是利用局部静态变量。

三、最推荐的懒汉模式3.0
#include<iostream>



class Singleton
{
public:
		//禁用拷贝构造和赋值构造函数
		Singleton(Singleton& s) = delete;
		Singleton& operator=(const Singleton&	s) = delete;	
		~Singleton(){std::cout<<"destruct start"<<std::endl;}	

		static	Singleton& getInstance();//声明接口为静态函数,通过类名访问
private:

	Singleton(){std::cout<<"construct start"<<std::endl;}

};

//类外实现
Singleton& Singleton::getInstance()
{
	static Singleton instance_;//在函数内部定义的静态变量只会被定义初始化一次,也就是说第一次执行会初始化和定义
	return instance_;//注意这里返回的时引用	
}	

int main()
{
	Singleton& s1 = Singleton::getInstance();//所以必须定义引用来使用
	Singleton& s2 = Singleton::getInstance();				
}	

代码虽然很简单,但是这里可以保证的是:只会返回一个实例,而且线程安全,而且也不会有内存问题。虽然可以使用返回指针的做法,但是并不推荐这么做,因为用户获取到对象以后万一使用delete就会有问题了。

单例模板的实现:

大名鼎鼎的ACE库中使用的单例其实就是用这种方式实现的。

看代码:

#include<iostream>


template<class T>
class SingletonTemplate
{
public:

	SingletonTemplate(SingletonTemplate& s) = delete;
	SingletonTemplate& operator=(const SingletonTemplate& s) = delete;

	static T& getInstance()
	{
		static T instance;
		return instance;
	}
	virtual ~SingletonTemplate()
	{
		std::cout<<"destruct start"<<std::endl;

	}



protected:
	SingletonTemplate(){
		std::cout<<"construct start"<<std::endl;
	}
};

class RealSingleton:public SingletonTemplate<RealSingleton>
{
//想要使用模板类很简单,声明模板类为友元类,然后私有化自己的构造函数
//这样在父类中就能使用该类的私有方法了
	friend class SingletonTemplate<RealSingleton>;





public:
	RealSingleton(RealSingleton& r) = delete;
	RealSingleton& operator=(const RealSingleton& r) = delete;


private:
	RealSingleton()=default;

};


int main()
{
	RealSingleton& r1 = RealSingleton::getInstance();
	RealSingleton& r2 = RealSingleton::getInstance();

}

ACE中基本就是采用这种思想去管理单例,这样就可以让用户类不用考虑单例的管理,但是它的做法还是较为复杂,还是这种的方式最为精简,所以说C++这种语言真的太可怕了,好像永远也不知道它到底还能怎么用。

知道我为什么这么说吗?你可能觉得这样已经完了,大神还有另外的操作呢!

####不需要使用友元的操作

#include <iostream>

template<typename T>
class Singleton{
public:
    static T& get_instance() noexcept(std::is_nothrow_constructible<T>::value){//这是告诉编译器函数没有异常的关键字
        static T instance{token()};
        return instance;
    }
    virtual ~Singleton() =default;
    Singleton(const Singleton&)=delete;
    Singleton& operator =(const Singleton&)=delete;
protected:
    struct token{}; //使用一个辅助类
    Singleton() noexcept=default;
};


/********************************************/
// Example:
// 这时候构造函数就算public也没事了,因为,用户必须要有token的权限,也就是说只有父类和子类能用


class DerivedSingle:public Singleton<DerivedSingle>{
public:
   DerivedSingle(token tt){//必须要提供token才能构造
       std::cout<<"destructor called!"<<std::endl;
   }

   ~DerivedSingle(){
       std::cout<<"constructor called!"<<std::endl;
   }
   DerivedSingle(const DerivedSingle&)=delete;
   DerivedSingle& operator =(const DerivedSingle&)= delete;
};

int main(int argc, char* argv[]){
    DerivedSingle& instance1 = DerivedSingle::get_instance();
    DerivedSingle& instance2 = DerivedSingle::get_instance();
    return 0;
}

总之:越小越好,越简单越好,线程安全,内存不泄露

写在最后

其实C++提供很多强大的技巧,但是有时候想想技巧真的就那么好吗,其实反思一下C++这么多年发展的却不是很好,也是有原因的,就是真的太难了,其实我觉得很少有人能在C++上配得上精通二字,因为这门语言真的很可怕,它随时可能带给你的是一种这样的感觉:**这又是啥啊,我学过吗,这确定是c++吗?**哈哈哈,其实我觉得C语言就很好,没有过的华丽的语法,依然不能阻碍它写出很优美的代码,很棒的设计思想,所以我觉得任何语言,我们不要追求很高超的技巧怎么怎么牛逼,如果没有什么实质性的作用,那么完全就是在炫技而已,看了redis,Nginx等优秀开源项目代码,才真的让我有一种佩服的感觉,当然C++也是可以写出超级优美的代码的,还是那句话,我们追求的不是炫技,而是写出很容易让人独懂的代码,即便注释很少,而且性能很高,再加上一些巧妙地设计。我觉得能达到这样境界的软件工程师才能担的上大牛二字。哈哈哈,我很菜,但是有一个成为大佬的梦想,继续学习,继续成长。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值