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 题解关…...

Mybatis逆向工程,动态创建实体类、条件扩展类、Mapper接口、Mapper.xml映射文件
今天呢,博主的学习进度也是步入了Java Mybatis 框架,目前正在逐步杨帆旗航。 那么接下来就给大家出一期有关 Mybatis 逆向工程的教学,希望能对大家有所帮助,也特别欢迎大家指点不足之处,小生很乐意接受正确的建议&…...

使用分级同态加密防御梯度泄漏
抽象 联邦学习 (FL) 支持跨分布式客户端进行协作模型训练,而无需共享原始数据,这使其成为在互联和自动驾驶汽车 (CAV) 等领域保护隐私的机器学习的一种很有前途的方法。然而,最近的研究表明&…...
Python爬虫实战:研究feedparser库相关技术
1. 引言 1.1 研究背景与意义 在当今信息爆炸的时代,互联网上存在着海量的信息资源。RSS(Really Simple Syndication)作为一种标准化的信息聚合技术,被广泛用于网站内容的发布和订阅。通过 RSS,用户可以方便地获取网站更新的内容,而无需频繁访问各个网站。 然而,互联网…...
基于Uniapp开发HarmonyOS 5.0旅游应用技术实践
一、技术选型背景 1.跨平台优势 Uniapp采用Vue.js框架,支持"一次开发,多端部署",可同步生成HarmonyOS、iOS、Android等多平台应用。 2.鸿蒙特性融合 HarmonyOS 5.0的分布式能力与原子化服务,为旅游应用带来…...
电脑插入多块移动硬盘后经常出现卡顿和蓝屏
当电脑在插入多块移动硬盘后频繁出现卡顿和蓝屏问题时,可能涉及硬件资源冲突、驱动兼容性、供电不足或系统设置等多方面原因。以下是逐步排查和解决方案: 1. 检查电源供电问题 问题原因:多块移动硬盘同时运行可能导致USB接口供电不足&#x…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...

微服务商城-商品微服务
数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...

【配置 YOLOX 用于按目录分类的图片数据集】
现在的图标点选越来越多,如何一步解决,采用 YOLOX 目标检测模式则可以轻松解决 要在 YOLOX 中使用按目录分类的图片数据集(每个目录代表一个类别,目录下是该类别的所有图片),你需要进行以下配置步骤&#x…...

从零实现STL哈希容器:unordered_map/unordered_set封装详解
本篇文章是对C学习的STL哈希容器自主实现部分的学习分享 希望也能为你带来些帮助~ 那咱们废话不多说,直接开始吧! 一、源码结构分析 1. SGISTL30实现剖析 // hash_set核心结构 template <class Value, class HashFcn, ...> class hash_set {ty…...
Axios请求超时重发机制
Axios 超时重新请求实现方案 在 Axios 中实现超时重新请求可以通过以下几种方式: 1. 使用拦截器实现自动重试 import axios from axios;// 创建axios实例 const instance axios.create();// 设置超时时间 instance.defaults.timeout 5000;// 最大重试次数 cons…...