五分钟了解单例模式 in C++【C++学习笔记】

82.单例模式 in C++

今天讨论的不是c++的语言特性,而是一种设计模式,即单例模式

🍅单例模式是什么

单例一个类的单一实例,而单例模式是指打算那个类或者结构体只有一个实例

💡💡💡C++中的单例只是一种组织一堆全局变量和静态函数的方法

🍅为什么要关心单例模式(为什么要用单例模式)

在处理数据时

  • 一方面,如果你是有一个对象的单一实例,那么只会有单一的数据集,然后可能有一些功能
  • 而另一方面,我们有代表数据的类成员变量,我们有成员函数,代表的是对特定的数据集执行操作,或者以其他方式于数据交互

就是这两种情况

💡我们不打算实例化一个特定类的多个实例(即是不需要多个实例),我们只想要它们自己的数据版本,这些操作将在这些数据上执行

🍅什么时候用单例模式

那么单例设计模式在哪里发挥作用呢?

💡💡💡当我们想要拥有应用于某种全局数据集的功能,且我们只是想要重复使用时,单例是非常有用的

有些单例的例子,比如一个随机数生成器类

  • 我们只希望能够查询它,得到一个随机数,而不需要实例化它去遍历所有这些东西
  • 我们只想实例化它一次(单例),这样它就会生成随机数生成器的种子,建立起它所需要的任何辅助的东西了

另一个例子就是渲染器,渲染器通常是一个非常全局的东西

  • 我们通常不会有一个渲染器的多个实例,我们有一个渲染器,我们向它提交所有这些渲染命令,然后它就会为我们渲染

而从根本上来说,单例的行为就像名称空间,单例类可以像名称空间一样工作,并没有要求单例类像普通类一样工作

💡💡💡故这里可以再次重申定义:C++中的单例只是一种组织一堆全局变量和静态函数的方法(本质上是在一个单一的名称空间下)。而这便是单例的意义

🍅💡如何编写单例

静态函数的使用也好像名称空间的使用啊

静态函数的调用如下面的例子一般,为SingleTon::get()便可调用,这乍一看不就是在一个名称空间SingleTon中定义了一个get(),然后将其调用么

故可如此记忆静态函数的使用

首先,单例不能有构造函数。因为如果有公共的构造函数,它就会允许被实例化(相当于是为了安全考虑,从根上就杜绝其实例化),而如何做到取消构造函数呢?

  • 💡把构造函数标记为private,这意味着该类不能在外部被实例化

下一步,是提供一种静态访问该类的方法,即是在此处的get()

  • 因此我们需要某种静态函数,返回这种特定类型的引用或指针(能用引用的地方其实都可以用指针来替代,引用只不过是为了方便而已)
    • 传统的方法就是简单的在私有成员例创建这个单例类的静态实例(惊了,在类里创建实例)
    • 然而因为这个实例是静态的,所以需要在某处定义它(在类外部,cpp文件内)

💡只要有了这个get()函数后,就可以写些任意数量的非静态方法,并通过get()函数访问它们了

#include <iostream>

class SingleTon {
public:
    //static在类里表示将该函数标记为静态函数
    static SingleTon &get() {
        return m_temp;
    }
    
	void Function() {}	//比如说这里有一些方法可供使用
    
private:
    SingleTon() {};	//将构造函数标记为私有
    static SingleTon m_temp;	//在私有成员里创建一个静态实例
}

SingleTon SingleTon::m_temp;	//像定义任何静态成员一样定义它

然后如果要使用,则搭配类中返回实例引用的get()函数来使用就可以了

//接着上面的函数接着写👆👇
int main() {
    SingleTon::get().Function();	//这般使用便可
}

如此便可静态地访问单例,就像它只有一个实例,因为我可以使用这个get()函数

其实这里还是有点不安全,因为如果手贱或者犯了些其他错误不小心又实例化了一个对象,则就不是单个实例了。

所以这里要对复制构造函数进行修改(复制构造函数即是在利用一个类构造另一个类(复制)时所用的构造函数,比如SingleTon(const SingleTon&) {}这般便是最简单的复制构造函数)

  • 所以这里通常要做的是,简单的删除复制构造函数

    SingleTon(const SingleTon&) = delete;
    

    在此处标记复制构造函数为delete

    让其= delete便可,这里应该是对=进行了重载实现这个功能,反正作为使用者用就完事了

这般操作,就使得该类无法被复制了

SingleTon temp2 = SingleTon::get();		//❌❌❌
//会报错,因为无法复制了

综上,总结方法论

  1. 将构造函数设为私有,因为单例类不能有第二个实例
  2. 提供一个静态访问该类的方法
    • 设一个私有的静态的实例并且在类外将其定义!
    • 然后用一个静态函数返回其引用or指针,便可正常使用了
  3. 为了安全,标记复制构造函数为delete(删除复制构造函数)

🍅更高端有用点的单例的应用例子

比如些一个函数来生成一个随机的浮点数(假设)

#include <iostream>

class Random {
public:
    Random(const Random&) = delete;	//复制构造函数标记为删除
    
    static Random &get() {
        return m_temp;
    }
    
    //假设这个函数是随机生成一个1数,然后返回
	void Float() {
        return m_RandomNum;
    }	
    
private:
    Random() {};	
    float m_RandomNum = 0.5; 	//不讨论随机数生成,就直接让其为0.5就好了,仅仅作为例子
    static Random m_temp;	
}

Random Random::m_temp;

int main() {
    float num = Random::get.Float();	//由此得到一个随机浮点数
}

🍅💡小技巧(?)

在使用get()函数时,我们还需要在类成员中有一个静态实例,然后还要在某个翻译单元中来定义它

所以可以直接对get()动手脚,直接在函数内定义静态实例就好了

static Random& Get() {
    static Random m_temp;
    return m_temp;
}

其中的单例只会在最初使用时创建一次

而这个单例的生命周期就是你的应用的生命周期

只要有了这个get()函数后,就可以些任意数量的非静态方法,并通过get()函数访问它们了

🍅把单例弄成类而不是名称空间,为什么?

为什么不把它弄成一个命名空间呢?同样的功能都可以实现。

答案是:确实可以

这两者在功能上没有区别,但是会少了public,private这些类的功能,用起来会比较不顺手且不安全(虽然有其他方法可以达成这个功能,但为什么不直接用类呢?)

🍅总结方法论

  1. 将构造函数设为私有,防止从外部被实例化
  2. 设置get()函数来返回静态引用的实例
    • 直接在get()中设置静态实例就可以了,在调用get()的时候就直接设置静态实例
  3. 标记复制构造函数为delete
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值