版权声明:本文为CSDN博主「Dachao1013」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/Dachao0707/article/details/103874954
目录
C++ 单例模式详解
一、单例模式:
二、单例的实现思路
二、懒汉模式2.0
三、最推荐的懒汉模式3.0
单例模板的实现:
写在最后
C++ 单例模式详解
一、单例模式:
什么是单例:在面向对象编程中,我们创建对象的过程就是创建一个类实例,一个类是可以创造很多的类实例的,所谓单例就是规定一个类就只能创建出一个实例对象,比如:打印机实例,只要有一个就行。
一些使用场景:
比如我们在使用数据缓冲区的时候,使用一个缓冲区对象,那么这时就应该往一个地方写入数据或读取数据;
比如一个打印机,很多人使用,但是我们只需要一个就可以了,所以只要一个打印机对象。
总之是这种多对一关系的,这个一就可以考虑使用单例来实现。
二、单例的实现思路
在c++中,创建一个对象,首先会先调用类的构造函数,那么,实现单例就是要把构造函数私有化,然后给外界提供一个接口用来返回这个类的实例对象,现在只要让接口每一次返回的都是同一个实例对象就行了,那么这个实例对象就应该被创建为一个静态对象,保存静态数据区。
#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
```cpp
#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
```cpp
#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++这种语言真的太可怕了,好像永远也不知道它到底还能怎么用。
知道我为什么这么说吗?你可能觉得这样已经完了,大神还有另外的操作呢!
####不需要使用友元的操作
```cpp
#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++也是可以写出超级优美的代码的,还是那句话,我们追求的不是炫技,而是写出很容易让人独懂的代码,即便注释很少,而且性能很高,再加上一些巧妙地设计。我觉得能达到这样境界的软件工程师才能担的上大牛二字。哈哈哈,我很菜,但是有一个成为大佬的梦想,继续学习,继续成长。