C++——特殊类设计

作者:几冬雪来

时间:2024年4月13日

内容:C++——特殊类设计

目录

前言:

特殊类设计: 

1.设计一个类,不能被拷贝

2.设计一个类,只能在堆上创建对象 

3.设计一个类,只能在栈是创建对象

4.设计一个类,不能被继承

5.设计一个类,只能创建一个对象(单例模式) 

结尾:


前言:

在前面的一个板块学习了C++的智能指针以及相关知识之后,在这一篇博客我们将学习C++中的一个拓展的知识板块——特殊类设计。 

特殊类设计: 

在这里的特殊类的设计,其目的是为了解决特殊场景下的特殊要求

而特殊类的设计,这里就依靠题目来对其进行一个大致的讲解。

1.设计一个类,不能被拷贝

首先这里要设计的第一个类就是使这个设计出来的类不能被拷贝。

在这里让一个类如何能不被拷贝,解决这个问题在C++中有两种方法

C++98与C++11都有解决的方法,而在这两种方法中C++11的方法更为容易

C++11在这个地方的做法就是在构造函数,拷贝构造或者赋值函数的后边加上一个delete,这里就能做到使得类能不被拷贝

而上述做法是C++11的解决方法,同样的设计设计一个类,让这个被设计出来的类不能被拷贝,在C++98中也有它自己的解决方法

在这里C++98中上图的两个函数是默认成员函数,即使不写编译器也会自动生成,因此要书写这两个函数。

但是书写之后不想让其进行拷贝,所以这里的解决方法就是只声明不实现或者不定义,且将其声明为私有,如果声明为公有的话,有可能被类外面定义实现,这里就不是防拷贝了

2.设计一个类,只能在堆上创建对象 

接下来的一个特殊类是让设计出来的类,让这个类只可以在堆上创建对象。 

正常情况下书写一个类,那么这个类在哪里都能创建对象,比如栈,堆与静态区都可以创建。 

如上图,我们就分别在栈,静态区以及new出来了一个类,这都是可行的。而如果要求只能创建在堆上边的该如何做?

在这个地方就会使用到一种非常巧妙的规避的手法来实现。

这里的栈和静态区所创建的类都会去自动调用析构函数,而new出来的对象需要手动去进行调用析构函数。

这里我们就可以利用这个原理,在类中的私有中加入其析构函数就能实现类只能在堆上进行创建的操作,因为这个操作间接栈和静态区上的对象封住了

以上的操作确实将栈上以及静态区上的对象封住了,但是这个地方要手动对hp3进行释放的话,单纯的进行hp3的delete是做不到的

这里不能直接使用delete删除数据是因为,类的析构函数在私有领域中,但是这里这里要记住私有限制的是类外面,在类外面并不能调用,但是类里面还是可以正常使用的

综上所述,这里的解决方法就是在类里面书写一个Destroy函数去delete的调用私有域中的析构函数

同时main函数中也要用指针将hp3传过去进行删除操作

并且这个地方还有另一种解决方法,上边这种方法是将类的析构函数进行私有化了,与之相对的,此处也可以将类的构造函数进行私有化

从上边这张图也可以看出来,即使类中私有化的不是析构函数而是构造函数代码进行一定的修改依旧可以完成要求。

但是要注意的是,如果不是析构函数私有化而是构造函数私有化的话,这里要提防赋值构造与拷贝构造,所以它们也要进行封锁

如上的两种方法就是设计一个类,使得这个类只能在堆上创建对象的实现方式和代码了。

3.设计一个类,只能在栈是创建对象

上边要求是要设计的一个类,且这个类只能在堆是创建对象,而先前也有说过,在正常情况下书写一个类,那么这个类在哪里都能创建对象,比如栈,堆与静态区都可以创建。

那么接下来就要设计一个类,然后让这个类只能在栈上创建对象

首先对比在堆上建立对象,在栈上建立对象无法从析构函数入手,也就是只能使用构造函数

所以这里可以根据书写堆一样,一开始先在私有中写入一个构造函数,接下来按照书写堆中公有的CreateObj函数之后,这里就能实现创建的对象只能在栈上。

但是这个地方设计一个类,只能在栈创建对象和只能在堆上创建对象有一个区别的点,那就是在只能使用栈创建对象的代码中,这里需要我们返回对象

但是,如果我们的代码这样子书写的话,就还会出现一些小问题

那么是什么问题呢?

在这里main函数中我们借由hp3来new一个对象,而这个对象可以去调用构造又或者是拷贝构造,而且其new出来的对象hp4则处于堆上

而这里有解决的方法,这里就要涉及以前学习的知识了,在C++中new由两个部分构成new一个部分是去调用operator的new,另一个部分要调用构造,但是这里构造被封死后还能调用拷贝构造,并且这里不能将拷贝构造也封起来,因为传值返回需要拷贝构造的存在

而解决这个问题的方法就是重载一个类的operator newoperator new默认调用全局,那么main函数在实现new的时候就不会去调用全局,而是调用类中书写的

这样子书写就能基本实现使类只能在栈上创建对象的操作,但是实际上这个地方还存在的类似上述的问题,比如在静态的拷贝构造等

4.设计一个类,不能被继承

接下来是设计一个类,使得该类创建出来的类不能被继承。 

那么如何让一个类不能被继承,这里C++98和C++11都有各自的解决方法

在这里C++98是使构造函数私有化派生类中调用不到基类的构造函数所以无法继承。而C++11则直接使用final关键词修饰,使之不能被继承

在这里C++98的构造函数私有化也是能做到,这是因为当我们把一个构造函数私有化了之后,而私有在派生类中是不可见,但是C++又规定了派生类必须调用父类的构造函数

又因为父类构造函数私有,导致子类不可见,派生类也就构造不出来对象

5.设计一个类,只能创建一个对象(单例模式) 

在讲解完了前4个特殊类之后,接下来讲解的是C++的另一个特殊操作——单例模式

这里的单例模式实际上涉及到了Linux中的多线程,而在没正式了解多线程之前,单例模式能听懂多少算多少。

而在C++中单例模式非常的常用,且它的设计也和类有所关系

首先是设计模式设计模式可以被类比为兵法。在一开始程序员书写代码的时候,代码并没有明确的书写方法和形式,当代码多起来的时候,在代码中发现有些地方有相同的书写方式,而且这种方式是需要被推广的,因此这里就出现了初始的23种设计模式

但是在这个地方C++对设计模式的要求不高,反倒是java对设计模式有较高的要求C++更加侧重单例模式的使用

那么什么是单例模式呢?

首先单例模式要保持当前的系统,也就是当前的进程里面这个类的对象只能有唯一一个,只能有唯一实例对象

为什么要有唯一实例对象。这里举一个例子:比如这里写了一个服,而这个服中有ip,地址等各种配置,整个程序则只需要一份配置文件。那么对这个配置文件进行写,读,获取配置信息等,我们就可以将其写成一个类,这个类全局只有一个唯一对象,那么这个时候保持的配置信息就是唯一的

而且单例模式可以运用在许多的地方,类似内存池的设计就是单例的,这里想要申请,释放,找内存池,整个进程就一个内存池给我们进行这些操作。如果这里搞出来多个内存池,多个内存池就会发生申请内存在A内存池,但是释放内存却在B内存池的操作

在这里假设我们有些信息要保存到这个类里面, 类似上图我们需要这个map的信息全局只有唯一实例的

保证该数据全局只有唯一一份,在正常情况下是保存不了的,因为正确情况下我们可以创建任意多个对象

这里就创建了多个对象,这里也没有固定要求必须是new等创建方式,这里创建类的要求是要有唯一对象情况完全不一致

这里的操作和前面的创建只能在栈,堆生成对象的类不一样,但是它们之间又存在相似之处,因此这里想解决这个问题也能参考上述的代码

要解决这个问题首先要进行的操作就是构造函数的私有化,这里构造函数私有化得出来的结果就是使得类无法随意创建对象

这里就只能通过自己书写的函数来构造,而且如果我们是在类中创建一个对象进行返回的话,那么程序每一次都在创建对象,那么怎么保证每一次获取的是同一个对象

在这里有两种方式来解决这个问题

首先这里的操作就是在创建一个类的全局对象,然后再在类中返回这个对象,这是因为全局的对象只有唯一一个。 

但是在这里使用这种方法去这样书写后出现一些问题,那就是它在类的外面无法调用构造函数,那么代码要怎么修改。

这里的改良方法就是在类中定义一个静态的对象,这里能在类中定义一个静态的对象是没有存在于类的空间中

但是要记住该操作只是声明,这里还需要再类的外面对其进行定义,也就是我们常说的类里面声明类外面定义

最后因为出了作用域,所以我们只需要引用返回该对象即可,而单例函数的第 二个需求就是要提供一个获取单例对象的接口函数,而且这样之后我们就可以去获取一个单例对象了。 

从结果是来看,在这里我们成功的获取了单例对象,并且通过取地址的操作也可以看出它们的地址是一致的,也就说明是同一个

在解决完了这一个问题之后,接下来就来看一下下一个问题,这里存在的问题就是没有将单例模式防住

在这里的问题依旧是拷贝构造,在这里虽然不能随意的创建对象,但是是可以做到拷贝构造对象的操作

这里的操作就是获取单例对象后再去进行拷贝构造的操作,这样子操作就不能保证全局是唯一一份了

因此这里第三步的操作就是防拷贝操作

因此为了防止拷贝构造的问题出现,在这里我们就要在私有域中书写防拷贝的代码,如上代码写完了之后才算正式的将单例模式的拷贝构造封住。

以上就是单例模式代码的写法,但是在这里单例模式中又被细分出来了几个模式,而上边的这种写法就是单例模式中的饿汉模式

这里的饿汉模式指的是一开始,也就是main函数之前就创建单例对象,如上图我们就是定义了一个静态对象,而静态对象等全局对象都是在main函数之前就创建好了的

main函数之前创建一般情况下没有问题,但是特殊情况可能会不好的方式,首先这里单例对象很大,或者初始化内容很多可能会影响启动速度,这是因为代码从main函数才算开始main函数之前都是准备工作,而饿汉模式可能会在代码开始前进行连接等操作

而前边这些只是一些小问题,到这里最严重的问题就是如果有两个单例类,但是两个单例类又互相有依赖关系的情况

例如有A,B两个单例类,这里要求A先初始化,而后B依赖AA需要连接数据库等操作B这里要求A处理完了再处理B,不然会报错,也就是两个单例类,要求A先创建,B再创建,B的初始化依赖与A

这里就会导致一个问题的出现,这里无法保证A一定先创建初始化后B再创建初始化.,因为它们都是受类域限制的静态成员对象

因此这里饿汉模式存在第二个问题,那就是初始化问题,当初始化有顺序要求的时候,饿汉模式可能会有所出错

而为了解决饿汉模式出现的这个问题C++中提出了单例模式下的另一种模式——懒汉模式。 

这里的懒汉模式可以说其他地方都一样,类似构造函数私有与防拷贝都与饿汉模式一样,唯一有区别的就是懒汉模式不在main函数之前创建对象

在这里懒汉模式的做法就是运用指针搞定,这里无论对象多大都初始化为指针,而这个对象相当于没有实际创建出来

这个对象不去调用就不会创建,同时因为构造函数私有化延伸出来的GetInstance函数也会受到影响,因此它也需要修改。

这里的做法就是使得对象第一次调用GetInstance的时候创建单例对象,也就是_psinst为空的时候进行创建,而且不为空的时候不创建

这里就是懒汉模式的实现方法,且完美解决了饿汉模式的两个问题,首先就是初始化内容多速度慢的问题,而在懒汉模式的main函数之前我们只初始化了指针

如果两个单例函数有依赖关系,这里就可以在进入main函数之后,先使用A进行GetInstance再使用B进行GetInstance

这里看似懒汉比饿汉好使用,但是实际上懒汉问题涉及了多线程以及枷锁等问题相比较饿汉模式懒汉模式更为复杂

在这里出现了一个新的问题,那就是懒汉模式的对象需不需要进行释放,在饿汉模式中我们的对象是直接定义出来的,不存在释放的操作,与之相对懒汉则是new出来的,这里需不需要进行释放呢? 

在这里一般的单例不用释放,这里程序正常结束会继续释放操作,但是一些特殊场景,例如中途需要显示释放又或者程序结束的时候,需要一些特殊动作(如持久化),在这些特殊情况就需要书写释放的代码

这里我们就书写一串释放的代码,如果这里对象_psinst的值不为空的话这里就进行释放的操作。而且这里的释放操作也是很简单,先对对象进行delete操作,在delete之后再将其置空即可。 

这样子在中途就可以显示调用,也可以用来解决特殊场景。 

接下来还有一个小问题,那就是如果我们不知道程序什么时候结束,又或者结束的地方很多,这里又该怎么做? 

如果不解决这个问题的话,假设有10个单例,这里就需要写10个DelInstance。 

这个地方没办法直接使用智能指针解决问题,因为使用智能指针会导致显示释放没办法书写。 

这里就是新增加的代码这里即使没有显示调用,代码也会正常的析构,同时对象也成功持久化了。 

这里和智能指针有些类似,这里创建了一个gc对象,而它出了作用域就会调用析构,而且该对象是全局对象,main函数结束它就会调用析构函数,而且析构函数就会去调用DelInstance函数

这里有多个单例对象就可以这样子书写,同时还存在另一种书写方法。 

这里我们可以将这一串代码嵌套到单例类的内部,并且定义一个静态的_gc对象,使它形成内部类来实现

也就是说每个单例类都可以带上_gc对象进行使用。 

结尾:

到这里我们的特殊类的设计就讲解完了,虽然特殊类设计是一个拓展的小知识点,但是在正常进行代码书写的时候,还是会经常使用到特殊类来处理问题的,也就是它也是我们要了解的一个知识,最后希望这篇博客能带来一些帮助。 

相关推荐

  1. C++特殊设计

    2024-04-13 14:56:03       35 阅读
  2. C++】特殊设计

    2024-04-13 14:56:03       31 阅读
  3. C++特殊设计

    2024-04-13 14:56:03       29 阅读
  4. C++特殊设计

    2024-04-13 14:56:03       30 阅读
  5. c++特殊设计

    2024-04-13 14:56:03       19 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-04-13 14:56:03       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-04-13 14:56:03       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-04-13 14:56:03       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-04-13 14:56:03       20 阅读

热门阅读

  1. Vue 3 + Vite项目实战:常见问题与解决方案全解析

    2024-04-13 14:56:03       12 阅读
  2. 【八股】MySQL

    2024-04-13 14:56:03       11 阅读
  3. 掌握 Python 迭代:循环和列表推导的魔力

    2024-04-13 14:56:03       11 阅读
  4. CSS学习笔记

    2024-04-13 14:56:03       16 阅读
  5. spring-ioc三层架构测试

    2024-04-13 14:56:03       14 阅读
  6. Python教程:深入了解Python垃圾回收机制

    2024-04-13 14:56:03       15 阅读
  7. 从零起步学C++笔记(一)-简单程序设计

    2024-04-13 14:56:03       14 阅读
  8. 【资料】华为硬件工程师手册与资料

    2024-04-13 14:56:03       14 阅读
  9. C++菜单查询

    2024-04-13 14:56:03       13 阅读
  10. Leetcode27题:移除元素【27/1000 python】

    2024-04-13 14:56:03       11 阅读
  11. Python脚本式编程

    2024-04-13 14:56:03       13 阅读
  12. 蓝桥杯备考随手记: 常见的二维数组问题

    2024-04-13 14:56:03       15 阅读
  13. 光伏发的电可以存储在哪里?

    2024-04-13 14:56:03       45 阅读
  14. linux c多进程通信之共享内存和信号量

    2024-04-13 14:56:03       17 阅读