C++虚函数操作指南
- 1 什么是虚函数?
- 1.1 虚函数的使用规则
- 1.2 用 C++ 运行虚函数的示例
- 1.3 协变式返回类型
- 2 在 C++ 中使用虚函数的优点
- 2.1 代码更为灵活、更为通用
- 2.2 代码可复用
- 2.3 契约式设计
- 3 虚函数的局限性
- 3.1 性能
- 3.2 设计问题
- 3.3 调试,容易出错
- 4 虚函数的替代方案
- 4.1 仅使用数据成员
- 4.2 变体
- 4.3 函数式编程
- 4.4 静态多态
- 5 用 Incredibuild 加速您的 C++ 虚函数
- 6 总 结
- 6.1 什么是 C++ 的虚函数?
- 6.2 虚函数存在哪些问题?
- 6.3 在 C++ 中,虚函数有何替代方案?
1 什么是虚函数?
虚函数是基类中声明的成员函数,且使用者期望在派生类中将其重新定义。那么,在 C++ 中,什么是虚函数呢?在 C++ 中,通常将虚函数用于实现运行时多态,该特性由 C++ 提供,适用于面向对象编程。我们将在下文更为详细地讨论运行时多态。不论函数调用所使用的指针或引用类型如何,虚函数最为重要的工作是确保函数调用正确。
1.1 虚函数的使用规则
C++ 虚函数必须遵循几个关键规则:
- [✔] 在基类中使用 virtual 关键词来声明函数
- [✔] 虚函数不能为静态函数
- [✔] 为实现运行时多态,应使用指针或引用来访问虚拟函数
- [✔] 对于基类和派生类而言,此类函数的原型应该相同(允许使用协变式返回类型,我们将在下文进行讨论)
- [✔] 如果基类中含有虚函数,则应该使用虚拟析构函数,防止析构函数调用错误
1.2 用 C++ 运行虚函数的示例
虚函数在 C++ 中的运行情况:
class Pet {
public:virtual ~Pet() {}virtual void make_sound() const = 0;
};class Dog: public Pet {
public:virtual void make_sound() const override {std::cout << "raf raf\n";}
};class Cat: public Pet {
public:virtual void make_sound() const override {std::cout << "mewo\n";}
};int main() {Cat mitzi;Dog roxy;Pet *pets[] = {&mitzi, &roxy};for(auto pPet: pets) {pPet->make_sound();}
}
解释一下上述示例。
Pet
这是一个通用基类。但是我们仍然希望存在一个 make_sound
函数,这样,我们就能在不知道 pet
类型的情况下,在 pet
上调用 make_sound
。仅在进行编译时,我们才能知道 pet
类型。因此,我们在基类中声明虚函数 make_sound
,用 =0
来将其表示为由派生类实现的纯虚函数。
然后,再由 Dog
和 Cat
来真正实现该函数。实现函数期间,我们添加关键词override
,这样,编译器就能确保函数签名与基类中的签名相匹配。
在 main
中,我们可以在 Pet
指针上调用 make_sound
,而无需在编译时知道该指针指向哪种 pet
。我们会在运行时,根据实际存在的对象,实现所需函数。
我们必须要强调,这是一个非常简单的示例。我们也有其他解决方案应对这一简单示例(例如,为 pet’s sound
持有数据成员,并避免使用虚函数)。但我们想要展示虚函数的实现过程,因此不对其他解决方案进行额外展示。通常情况下,会使用虚函数为派生类中的不同行为建模,而相应行为不能用简单数据成员来建模。
1.3 协变式返回类型
我们提到过,若要实现虚函数,派生类函数的签名必须与基类中的签名相匹配。 唯一允许的区别是在返回类型上,只要派生类的返回类型是基类返回的派生类型即可。让我们看看下面的示例:
class PetFactory {
public:virtual ~PetFactory() {}virtual Pet* create() const = 0;
} class DogFactory: public PetFactory {
public:virtual Dog* create() const override {return new Dog();}
}; class CatFactory: public PetFactory {
public:virtual Cat* create() const override {return new Cat();}
};int main() {std::vector<Pet*> pets;DogFactory df;CatFactory cf;PetFactory* petFactory[] = {&df, &cf};for(auto factory: petFactory) {pets.push_back(factory->create());}for(auto pPet: pets) {pPet->make_sound();}for(auto pPet: pets) {delete pPet;}
}
在上述示例中,PetFactory
创建函数仅能知道它可以返回 Pet*
,但使用协变式返回类型,DogFactory
和 CatFactory
则能知道更为具体的内容,这种虚函数的实现方式仍然行之有效。
2 在 C++ 中使用虚函数的优点
现在,如果您已经花费时间研究过 C++,可能会注意到,不需要由虚函数来重新定义派生类中的基函数。但存在这样的巨大区别,使得虚函数不可或缺:虚函数覆写基类函数,从而实现运行时多态。从本质上讲,多态指一个函数或对象以不同方式执行的能力,具体情况视使用方式而定。这属于面向对象编程的关键特性——结合其他众多特性,使得 C++ 作为编程语言而有别于 C 语言。
2.1 代码更为灵活、更为通用
这是贯穿所有多态程序的主要优点:根据运行时已知的调用对象,通过允许以不同方式执行函数调用,能使程序更为灵活而通用。如此一来,运行时多态便能从真正意义上使您的代码反映现实——特别是各场景中的对象(或人、动物、形状)并不总是以相同方式执行。
2.2 代码可复用
通过使用虚函数,我们可以将只应实现一次的通用操作和不同子类中可能有所不同的具体细节区分开来。试想以下示例:如果我们希望实现prism
类层次结构,则需要在各派生类中分别计算基面积,但可以使用派生类实现基面积计算,从而在基类中实现体积函数。实现代码如下:
class Prism {double height;
public:virtual ~Prism() {}virtual double baseArea() const = 0;double volume() const {return height * baseArea();}// ...
};class Cylinder: public Prism {double radius;
public:double baseArea() const override {return radius * radius * std::numbers::pi}// ...
};
2.3 契约式设计
术语“契约式设计”指如果代码设置有执行设计的契约,会比只通过文档来执行设计要好得多。虚函数,特别是纯虚函数,因其决定了在派生类中以不同方式重新实现特定操作的设计决策,可将其视为契约式设计工具。
3 虚函数的局限性
虚函数功能极为强大,但它们并非毫无缺点。开始使用虚函数前,您应该注意以下事项:
3.1 性能
无论是在运行时性能还是在内存方面,虚函数成本都要比普通函数高。
内存部分通常冗余,取决于实现方式,但最为常见的是每个对象都有一个额外内部指针。这并不是什么大问题,除非我们有数以百万计的小对象,这些小对象的额外指针可能会引起内存问题。
函数的运行时性能成本不是一次跳转而是两次跳转,或者如果可以内联函数,性能成本就是两次跳转而不是零次跳转。虚函数需要跳转到虚函数表,再跳转到函数本身。这种额外跳转增加了 CPU 指令缓存中指令未准备就绪的概率,因此,这两次跳转并非唯一成本。
最后,如果您需要实现多态,与其他替代方案相比,性能方面的额外成本通常也在情理之中。然而,若要将第一个虚函数添加到类中,通常需要考虑额外成本。
3.2 设计问题
继承,特别是虚函数,会引起设计问题。继承层次结构设计糟糕可能会导致类膨胀和类之间关系异常。
从构造函数和析构函数调用虚函数的规则也会影响您的设计。从构造函数和析构函数调用的任何虚函数都不是多态函数,这样一来,有时需要将操作从构造函数转移到 init
虚拟函数。
为避免糟糕设计,应切记继承和多态并非是应对任何问题的最佳解决方案。
3.3 调试,容易出错
讽刺的是,虚函数面临的挑战之一是缺乏弹性。
由于需要遵循调用流程,调试虚函数调用可能会变得稍显混乱。一般来说,遵循函数调用并不十分困难,但根据对象类型,在遵循隐藏调度方面,仍然需要进行额外工作。调试器会自行纠正错误,但决定断点位置可能会变得更加困难。
至于更容易出错,在某些情况下,不应调用虚函数的基类实现;而在某些情况下,应在开始时调用,有时也在结束时调用。由于忘记调用基类实现,或是在错误的地方、不需要的时候调用,使用虚函数极其容易出错。
可将其视为契约式设计工具。
4 虚函数的替代方案
4.1 仅使用数据成员
第一种替代方案是尝试并对基于简单数据成员的不同行为进行建模。如果不同类型的唯一区别是 sound
,那就将其转换为数据成员,在构造时进行初始化,这样就没有问题了。但在许多情况下,行为更加复杂,需要不同的实现方式。
4.2 变体
另一种方案是使用 std::variant
和 std::visit
,特别是待支持的不同类型已知,且列表不会太长时,二者可能相关。您可以点击此处和此处关于该方案的信息。
4.3 函数式编程
您可以传递待执行的操作,将其作为函数对象的 lambda
,或者作为旧有 C 样式的函数指针,随后对其进行建模,而无需在类层次结构中对不同操作进行建模。通过该方法,您能将数据模型和可能想要执行的操作区分开来,这带来了极高灵活性。
4.4 静态多态
静态多态是一种基于模板的方法,用于获取多态动态,但基于编译时已知的实际想要使用的类型。例如,您可能希望代码同时支持 UDPConnection
和 TCPConnection
,但在编译时,您可能想要知道使用 UDPConnection
或 TCPConnection
的具体流程。基于模板的静态多态可以实现更佳性能。
一些替代技术可能会导致项目编译时间变长。我们认为,特别是当您使用 Incredibuild
来加速构建时,这不会影响您的决策设计。首先选择合适的设计方案,然后使用正确工具来缩减编译时间即可。
5 用 Incredibuild 加速您的 C++ 虚函数
如果您想在不严重拖累编译速度和构建进程的情况下,从虚拟函数中受益,您就需要强大的计算能力作为后援。
Incredibuild
能够做到这一点。通过在虚拟机在本地网络上分配编译任务,Incredibuild 从根本上加快了 C++ 的编译速度。此外,Incredibuild
能与时下主流编译器和构建系统无缝集成,包括 Visual Studio、Qt Creator
和 Clang
。
如此一来,虚函数便能具备极高的灵活性和效率,而无需花费时间来等待代码编译。首先选择合适的设计方案,然后使用正确工具来缩减编译时间即可。
6 总 结
6.1 什么是 C++ 的虚函数?
虚拟函数是基类中声明的成员函数,将在派生类中重新定义。在 C++ 中,使用虚函数来实现运行时多态。
6.2 虚函数存在哪些问题?
在运行时性能和内存使用方面,相比于普通函数,虚拟函数会造成更多影响。此外,虚拟函数会产生基于继承层次结构的设计问题,导致类膨胀和关系异常。最后,虚拟函数由于存在函数调用问题,往往难以进行调试。由于调用顺序的不可预测性,使用虚拟函数更容易引发错误。
6.3 在 C++ 中,虚函数有何替代方案?
是的,为了实现更好的设计或者是获得更佳的性能,您可能要考虑一些替代方案。但鉴于 C++ 程序员普遍使用虚函数,您应将其视为工具包内的一项工具,必要时加以使用。
如果您选择了另一种替代方案,比如基于模板的静态多态,切勿让较长的编译时间影响您的设计方案。确保选用合适的工具来加速构建进程,如果您没有使用 Incredibuild
,请了解我们的解决方案,看看 Incredibuild
在减少编译时间方面可实现的惊人效果。
相关文章:
C++虚函数操作指南
1 什么是虚函数?1.1 虚函数的使用规则1.2 用 C 运行虚函数的示例1.3 协变式返回类型2 在 C 中使用虚函数的优点2.1 代码更为灵活、更为通用2.2 代码可复用2.3 契约式设计3 虚函数的局限性3.1 性能3.2 设计问题3.3 调试,容易出错4 虚函数的替代方案4.1 仅…...
Mybatis-Plus分页插件
引言:MyBatis Plus自带分页插件,只要简单的配置即可实现分页功能 1.添加Configuration配置类 Configuration MapperScan("com.atguigu.mybatisplus.mapper") //可以将主类中的注解移到此处public class MybatisPlusConfig {Beanpublic Mybatis…...
Selenium Webdriver options的实用参数设置
1、关闭Chrome浏览器受自动控制的提示 options.add_experimental_option(useAutomationExtension, False) options.add_experimental_option(excludeSwitches, [enable-automation])2、关闭是否保存密码的弹窗 options.add_experimental_option("prefs", { "c…...
代码随想录算法训练营第七天|454.四数相加II 、 383. 赎金信 、 15. 三数之和 、18. 四数之和
454.四数相加II 454.四数相加II介绍给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:思路因为是存放在数组里不同位置的元素,因此不需要考虑去重的操作,而…...
详解抓包原理以及抓包工具whistle的用法
什么是抓包? 分析网络问题业务分析分析网络信息流通量网络大数据金融风险控制探测企图入侵网络的攻击探测由内部和外部的用户滥用网络资源探测网络入侵后的影响监测链接互联网宽频流量监测网络使用流量(包括内部用户,外部用户和系统)监测互联网和用户电脑的安全状…...
【C++】反向迭代器
文章目录一、什么是反向迭代器二、STL 源码中反向迭代器的实现三、reverse_iterator 的模拟实现四、vector 和 list 反向迭代器的实现一、什么是反向迭代器 C 中一共有四种迭代器 – iterator、const_iterator、reverse_iterator 以及 const_reverse_iterator,其中…...
(蓝桥真题)扫描游戏(计算几何+线段树二分)
题目链接:P8777 [蓝桥杯 2022 省 A] 扫描游戏 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 样例输入: 5 2 0 1 1 0 3 2 4 3 5 6 8 1 -51 -33 2 样例输出: 1 1 3 4 -1 分析:先考虑如何对物件进行排序,首先&…...
面试官:什么是双亲委派模型?如何打破它?
本文已经收录进 JavaGuide(「Java学习+面试指南」一份涵盖大部分 Java 程序员所需要掌握的核心知识。) 参加过校招面试的同学,应该对这个问题不陌生。一般提问 JVM 知识点的时候,就会顺带问你双亲委派模型(别扭的翻译。。。)。 就算是不准备面试,学习双亲委派模型对于我…...
自建服务器系列- DDNS配置
1、环境说明 光猫桥接路由器拔号的模式 2、DDNS是什么 对于DHCP方式获得的IP,无论对于局域网内来说,还是外网来说,都会有使得IP地址每隔一段时间变化一次,如果想要通过恒定不变的地址访问主机,就需要动态域名解析。…...
vue中使用axios简单封装用法,axios报错the request was rejected because no multipart boundar
在这里插入代码片## 创建实例 //这个写法作为我错误的记录,可以不看暂时 transformRequest: [(data: any) > {if (!data) {data {}}return qs.stringify(data)}]在我的项目里面,初始化配置里面进行handers的修改,例如:例如将…...
Leetcode.1220 统计元音字母序列的数目
题目链接 Leetcode.1220 统计元音字母序列的数目 Rating : 1730 题目描述 给你一个整数 n,请你帮忙统计一下我们可以按下述规则形成多少个长度为 n的字符串: 字符串中的每个字符都应当是小写元音字母(a, e, i, o, u)…...
深入元空间
元空间是干嘛的?元空间存储的是类的相关信息,就是类的运行时表达。包括:Class文件类的结构和方法常量注解代码优化JDK1.8分界在1.8版本之前,类的meta信息、类变量、字符串常量池都存储在永久代。1.8版本以后,类变量、实…...
前端技术和框架
一、各种技术概述 1.HTML 🧨HTML中文称为超文本标记语言,从语义上来说,它只是一种是一种标识性的语言,并不是一种编程语言。 <p>这是一段话</p>通过这个标签可以表示文本的一个段落。而且其中还有还有图片标签、视…...
02从零开始学Java之Java到底是个啥?
博主简介我是壹壹哥(孙玉昌),十年软件开发授课经验,CSDN博客专家、阿里云专家博主、掘金优秀创作者、infoQ专家博主;关注壹壹哥(孙玉昌),带你玩转Java,轻松实现从入门到放弃,哦不,到熟悉&#x…...
KEIL5中头文件路劲包含问题
方式1:1.Keil中添加头文件相对路劲的方法在c/c配置中添加路劲,最终是将添加的绝对路径转化为相对路径;注意:相对路径的当前位置指.uvproj文件所在位置在C/C配置中的include paths”中添加工程所用的所有头文件的路径;2…...
机智云目前我用过最便捷的物联网快速开发方案
GE211 MINI DTU上手来看,是一款尺寸比较小巧的模块,适合放置在几乎所有白色家电中,通过ph2.0端子(注意不要买错)引出了5v、gnd、tx、rx。可以说是非常方便了。下面正式开始我们的接入流程:首先注册一个机智…...
MySQL基础篇1
第1章 数据库介绍 1.1 数据库概述 什么是数据库? 数据库就是存储数据的仓库,其本质是一个文件系统,数据按照特定的格式将数据存储起来,用户可以对数据库中的数据进行增加,修改,删除及查询操作。 数据库分两…...
AQS 源码解读
一、AQS AQS 是 AbstractQueuedSynchronizer 的简称,又称为同步阻塞队列,是 Java 中的一个抽象类。在其内部维护了一个由双向链表实现的 FIFO 线程等待队列,同时又提供和维护了一个共享资源 state ,像我们平常使用的 ReentrantLo…...
使用 DataLoader 加载数据报错‘expected sequence of length 4 at dim 1 (got 0)’
使用 transformer 将字符串转为 id 序列,字符串为中英文混杂形式, 运行中出现报错:expected sequence of length 4 at dim 1 (got 0) 发现是在encoder_plus转换时,将输入的文本根据max_length截断了,导致[MASK]等字段…...
第十四届蓝桥杯第三期模拟赛B组C/C++原题与详解
文章目录 一、填空题 1、1 找最小全字母十六进制数 1、1、1 题目描述 1、1、2 题解关键思路与解答 1、2 给列命名 1、2、1 题目描述 1、2、2 题解关键思路与解答 1、3 日期相等 1、3、1 题目描述 1、3、2 题解关键思路与解答 1、4 乘积方案数 1、4、1 题目描述 1、4、2 题解关…...
致敬三八女神节,致敬IT女生
前言 三八女神节是一个特别的节日,它是为了纪念所有的女性,表达对她们的尊重和关爱。在这个特别的节日里,我们想要致敬所有在IT领域中奋斗的女生,她们用自己的智慧和努力为这个世界带来了无限的可能。 IT女神 从事IT行业的女生…...
【Go语言学习笔记】数据
目录字符串数组数组初始化指针复制切片基本操作resliceappendcopy字典deletemap是引用类型并发操作字符串 字符串是不可变字节(byte)序列,其本身是一个复合结构 type stringStruct struct{str unsafe.Pointerlen int }头部指针指向字节数组…...
puzzle(0919)六宫数局
目录 六宫数局 示例题目 简单模式 普通模式 困难模式 六宫数局 最强大脑同款项目。 找出一条给定起点和终点的路径,每一步的方向任选,在这个方向上移动的步数是当前数的质因数分解中2、3、5的次数。 示例题目 按照六边形坐标系来建立坐标系&#…...
脑机接口科普0016——独立BCI与非独立BCI
本文禁止转载!!!! 所谓的“独立BCI”与“非独立BCI”仅仅是BCI系统中的一个术语。本章主要是介绍一下这两个术语。 这两个术语是由Wolpaw在2002年提出来的。 独立BCI是指不依赖于中枢神经系统的的输出。 非独立BCI是指那种依赖…...
女神节告白代码
今天是女神节,送给所有女神们一句话: 爱自己是终生浪漫的开始,无论何时都要好好爱自己 目录 1. 请求动画帧填充 2.点类 3.粒子类 编辑 4.ParticlePool 池类 5.创建和填充 6.处理循环队列 7.更新活动粒子 8.移除非活性粒子 9.绘制有…...
【数据结构】单链表:头部操作我很行,插入也不用增容!!!
单链表 文章目录单链表1.链表1.1链表的概念和结构1.2链表的分类2.单链表的模拟实现2.1单链表的打印2.2单链表的尾插2.3单链表的头插2.4单链表的尾删2.5单链表的头删2.6单链表的查找2.7单链表的中间插入(在结点前插入)2.8单链表的中间删除(删除该结点)2.9单链表的中间插入(在结点…...
SpringBoot——使用WebSocket功能
springboot自带websocket,通过几个简单的注解就可以实现websocket的功能; 启动类跟普通的springboot一样: /*** 2023年3月2日下午4:16:57*/ package testspringboot.test7websocket;import org.springframework.boot.SpringApplication; im…...
博弈论小课堂:非零和博弈(实现双赢)【纳什均衡点】
文章目录 引言I 非零和博弈1.1 囚徒问题1.2 博弈中双方的收益矩阵II 在现实中找均衡点2.1 博弈通常不是一次性的,而是反复进行的2.2 博弈论讲的都是阳谋的策略2.3 人类还处于文明的初级阶段,人的道德水准不容高估2.4 乌合之众效应2.5 很多时候看似是双赢,其实是在更大范围内…...
数组中的逆序对
解题思路1: 看到这个题目,我们的第一反应是顺序扫描整个数组。每扫描到一个数组的时候,逐个比较该数字和它后面的数字的大小。如果后面的数字比它小,则这两个数字就组成了一个逆序对。假设数组中含有n个数字。由于每个数字都要和…...
C++基础了解-01-基础语法
基础语法 一、基础语法 C 程序可以定义为对象的集合,这些对象通过调用彼此的方法进行交互。现在让我们简要地看一下什么是类、对象,方法、即时变量。 对象 - 对象具有状态和行为。例如:一只狗的状态 - 颜色、名称、品种,行为 -…...
中国建设银行个人登录/无锡网络优化推广公司
Spring Boot总体来说,搭建还是比较容易的,特别是Spring Cloud全家桶,简称亲民微服务。但在发展趋势中,容器化技术已经成熟,面对巨耗内存的Spring Boot,小公司表示用不起。如今,很多刚诞生的JAVA…...
app store下载官方/广州seo网站营销
注:页面需提前引用JQ ; $.fn.extend({/*** notes: 获取13位时间戳的简单操作** new Date(2018-02-01 15:10:00).getTime() // Date类型使用getTime方法** "/Date(1517469000000)/".substring(6,19) // C#后台返回的DateTime数据*//*** author:lttr <ww…...
网站大全全部免费/北京刚刚传来特大消息
在创建实例属性时,如果直接把实例属性暴露出去,虽然写起来简单,但是存在一些风险,比如实例属性可以在外部被修改。为了限制外部操作,可以通过一个set_score()方法来设置成绩,再通过一个get_score()来获取成…...
河北建设厅网站修改密码在哪/如何查询百度搜索关键词排名
前言 这里有关于block的5道测试题,建议你阅读本文之前先做一下测试。 先介绍一下什么是闭包。在wikipedia上,闭包的定义)是: In programming languages, a closure is a function or reference to a function together with a referencing environment—…...
对网页设计作品的意见/seo代理计费系统
苹果电脑(mac os x)键盘使用技巧启动电脑时的巧妙使用:1、在电脑启动时,同时按住“option”键可以重建桌面,此操作应每月做一次。2、在电脑启动时,按住“shift”键可以关闭所有系统功能扩展。3、在电脑启动时ÿ…...
关于建设校园网站申请报告/邯郸seo营销
实验3-8 输出三角形面积和周长 (15 分) 本题要求编写程序,根据输入的三角形的三条边a、b、c,计算并输出面积和周长。注意:在一个三角形中, 任意两边之和大于第三边。三角形面积计算公式:area√s(s−a)(s−b)(s−c)&…...