C++干货系列——override和final让你的虚函数更安全

final与override final关键字用于防止类被继承或防止成员函数被重写。override关键字用于显式声明一个成员函数是重写基类中的虚函数,帮助编译器进行检查。通过这两个关键字,C++提供了更强的类型安全和代码可读性,减少了由于继承和多态带来的潜在错误。 阅读详情

今天我想谈谈override和final,这一对在C++11中不太引人注意的特性,相比于C++11其他特性而言也更简单。这两个特性都能让我们的程序在继承类和覆写虚函数时更安全,更清晰。

覆写(override)虚函数

你有没有像我一样遇到过这种情况:在子类中,本来你想覆写虚函数,结果虚函数却没有被正确地调用呢?或者更惨的是,你有时不得不去修改父类虚函数的声明。在所有的子类中查找重载的函数这件事可真的是很麻烦,而且墨菲定律告诉你:你永远会忘掉你搞错了的那一个子类。不管怎么样,看下边这个例子吧:

 struct Base {
   virtual void doSomething(int i) const {
     std::cout << "This is from Base with " << i << std::endl;
   }
 };
 
 struct Derivied : Base {
   virtual void doSomething(int i) {
     std::cout << "This is from Derived with " << i << std::endl;
   }
 };
 
 void letDoSomething(Base& base) {
   base.doSomething(419);
 }
 
 int main() {
   Derived d;
   letDoSomething(d);  //输出结果: "This is from Base with 419"
 }

相信我,我曾经傻傻地花了好久解决诸如上边的问题——当然是在更复杂的程序中。如果你现在还没发现问题,我提示你一下:Derived::doSomething函数把Base::doSomething的const给搞丢了。所以他们两者并没有相同的函数签名,前者也没有如我们预想的对后者进行覆写。确实会有编译器弹出warning提示这个问题,但是往往在我们本意就不想覆写虚函数时它也会报警——久而久之我们就麻木了,编译器并不能从根本上解决这个问题。像这样的场景,我们也没有工具来区分告诉编译器我们的本意是否向覆写这个父类的虚函数。因此,在C++11中,我们引入了override这个关键字。

 struct Derived : public Base {
   void doSomething(int i) override {  // ERROR,编译器报警没有正确覆写Base::doSomething
     std::cout << "This is from Derived with " << i << std::endl;
   }
 };

很简单吧,加个关键字,让编译器来检查我们又没有正确覆写父类的虚函数。因此,任何子类覆写虚函数后导致函数签名的变化,都会导致编译器报错。

除此之外,如果你一直使用override,他还会给你带来一个意想不到的收获:在C++11之前,关于子类覆写父类虚函数后,子类的虚函数还要不要加virtual关键字,还是个值得争论的问题。人们一开始之所以想在子类的覆写函数上也加上virtual,就是为了提醒读代码的人这是个覆写的虚函数。但是在有了override之后,这个关键字本身就起了这个作用,之前的做法也就不是必须的了。所以建议的做法是,在最顶层的虚函数上加上virtual关键字后,其余的子类覆写后就不再加virtual了,但是要统一加上override

Prefer to use override whenever you are overriding a virtual function andvirtual only for the topmost declaration of that function.

防止覆写

针对上面特性的反面,C++11也他讨论了如何防止子类再覆写父类的虚函数了——即父类的某些特性或方法被设计成不想再被改变。在C++11出现前,比较难受的一点就在于,即使父类的虚函数是private的情况下,我们仍然无法阻止它的子类覆写这个方法。

 class Base {
 public:
   void doSomething() const {
     std::cout << "I did something" << dontChangeMe() << std::endl;
   }
 private:
   virtual int dontChangeMe() const = 0;
 };
 
 class ChildOfBase : public Base {
 private:
   int dontChangeMe() const override { return 419; }
 };
 
 class BadChildOfChild : public ChildOfBase {
   int dontChangeMe() const override { return 61; }
 };
 
 int main() {
   BadChildOfChild badLittleChild;
   badLittleChild.doSomething(); //结果是61
 }

在C++11之前,你还对上边的行为无可奈何,至少你无法在语法层面去禁止开发人员这么做。现在,我们有了final关键字来帮我们这个忙。

 class ChildOfBase : public Base {
 private:
   int dontChangeMe() const final { return 61; }
 };
 
 class BadChildOfChild : public ChildOfBase {
   int dontChangeMe() const override;  //ERROR
 }

关于final和override关键字位置的一个小提示:这两者应放在在const,volatile等其他关键字后边,但是应该在纯虚标记,也就是"=0"的前边。一个final的纯虚函数是没什么意义的,因为本身就是一个抽象函数又不让后边的子类覆写给与它实际意义。另外就是override final和final override并没有什么区别,只是后者读起来可能更顺一些吧。只写一个final并不会像override那样检查覆写类型,所以最好还是两个都写上。

现在再去覆写ChildOfBase类的dontChangeMe函数就不可能了,但是写一个新的子类继承Base再覆写dontChangeMe还是允许的。

被final修饰的类

final还有第二种用法,直接用在类上,紧跟着类名,表示这个类禁止任何其他类继承它,无论是public继承还是private继承。

 class DontDeriveFromMe final {
   // ...
 };
 
 class Failure : public DontDeriveFromMe { //ERROR
   // ...
 };

但是在你给类使用final之前,还是麻烦你想清楚final是否真的是必要的,如果不是,那它就是一个坑。

如何更新你的代码

如果你的代码已经非常冗长并且到处都是覆写的虚函数,这个时候你想用final和override来更新你的代码库,我会给你以下建议:

  • 在处理final时应该慎之又慎,需要case by case地处理。
  • 至于override,无论哪里用到了覆写,直接加上去就完事了,只会有益处而不会有害,不过看清楚你的本意到底是不是覆写哈。尤其是你加上override后编译器报错的,一定要先检查你的本意到底是不是覆写,确定你真的犯了错误后,再去修改源代码。
  • 加上override后,我建议你把子类的virtual关键字去掉,并且以后都遵循上边提到的规范:只有顶层的虚函数加上virtual,其余子类在覆写时全部用override来标记。
  • 当你看到一个函数标记了virtual时,弄清楚它是不是顶层虚函数其实是一件非常困难的事情,这个时候我建议你多用用你的编译器,当你使用override报错时,也有可能它确实就是顶层virtual函数了。

结论

override和final都能帮助我们改善虚函数相关的安全性,使用final需要更多的权衡但是override就尽管大胆地用吧。

什么是重载(Overload)和重写(Override)?final、finally、finalize有何区别? 总之,Java中的异常处理机制通过try-catch-finally语句块和异常类实现了异常的捕获、处理和抛出,保证了程序的正确性和稳定性。Java中的异常处理机制是一种结构化的、面向对象的异常处理机制,它通过try-catch-finally语句块和异常类来实现异常的捕获、处理和抛出,保证程序的正确性和稳定性。重载的作用是提供一种便利的方式来实现功能的多样化,同一个类中的不同重载方法可以根据不同的参数来执行不同的操作,提高了代码的可读性和可维护性。 阅读详情

相关推荐

C++多态机制深度解析:从虚函数表到实战应用

多态是面向对象编程的核心概念,它允许同一接口在不同上下文中表现出不同行为,通过动态绑定机制实现。其技术价值在于提升代码的抽象能力、可扩展性和可维护性,是构建模块化系统的基石。在C++中,多态主要通过虚函数和继承机制实现,编译器会为包含虚函数的类生成虚函数表,对象通过虚函数表指针在运行时确定调用哪个函数。这一机制广泛应用于图形界面框架、插件系统和算法策略等场景,例如在Qt框架中实现自定义控件绘制,或在OpenCV中处理图像算法。理解虚函数表的内存布局和动态绑定原理,能帮助开发者编写更高效、更灵活的代码,同时避

weixin_34054931的博客 876

【C++11】final与override关键字和类的新功能

final与override关键字和类的新功能详解

枫叶先生的博客 1478

C++多态机制深度解析:从虚函数表到设计模式实战

面向对象编程中的多态性是实现代码灵活性和可扩展性的核心技术,它允许同一接口在不同上下文中表现出不同行为。其实现原理基于虚函数和虚函数表机制,通过运行时动态绑定实现派生类函数的正确调用。这一机制在提升代码复用性和维护性方面具有重要价值,广泛应用于图形界面、插件系统、游戏引擎等需要处理多种对象类型的场景。本文以C++为例,深入剖析虚函数表的内存布局和工作原理,并结合对象切片、虚析构函数等常见陷阱,帮助开发者掌握多态的正确使用方式。通过理解虚函数表指针和动态绑定的底层机制,读者能够更好地运用多态特性优化软件设计。

weixin_29258297的博客 242

C++中的final和override关键字详解

可以在编译时进行检查,确保派生类中的函数确实是基类中虚函数的重写版本。可以确保某个类不被继承,这在设计中有时候是有必要的,例如为了保护某些核心功能不被修改或破坏。是两个关键字,用于对类和类成员进行修饰,提供了更强的编译时检查和更灵活的代码设计。用于修饰类,表示该类不能被继承。它将该类标记为最终类,禁止其他类从它派生。可以帮助确保派生类正确地重写了基类中的虚函数,提高代码的可读性和可维护性。用于标记派生类中的虚函数,确保它是基类中虚函数的重写版本。用于修饰虚函数,表示该虚函数在派生类中不能被重写。

N阶二进制的博客 1916

C++继承机制深度解析:从语法到内存模型与多态实现

面向对象编程(OOP)的核心机制之一是继承,它允许新创建的类(派生类)基于现有类(基类)构建,从而实现代码复用和层次化设计。其基本原理是通过派生类继承基类的成员,并可以添加或重写功能,这为软件的可扩展性和维护性奠定了基础。从技术价值看,继承不仅是实现代码复用的关键手段,更是构建多态性的基石,通过虚函数和动态绑定,程序能在运行时根据对象实际类型调用相应方法,极大地提升了代码的灵活性和可维护性。在实际应用场景中,继承广泛用于框架设计、库开发和大型软件系统的架构,例如在GUI库中定义通用控件基类,或在游戏引擎中构

weixin_33951761的博客 465

【C++11保姆级教程】final和override

在C++编程中,我们常常需要使用继承和多态来构建复杂的程序。然而,在继承关系中,有时候我们需要对继承层次做出一些限制,确保某些类或函数不能再被继承或重写。为了解决这个问题,C++11引入了两个关键字,分别是final和override。本篇教程将向大家介绍final和override的具体用法,帮助大家更好地理解和应用这两个关键字。final和override关键字在C++中提供了对类继承和函数重写行为的额外控制。

m0_62599305的博客 4206

C++ override和final详解

像这样的场景,我们也没有工具来区分告诉编译器我们的本意是否向覆写这个父类的虚函数。因此,在C++11中,我们引入了。在所有的子类中查找重载的函数这件事可真的是很麻烦,而且墨菲定律告诉你:你永远会忘掉你搞错了的那一个子类。针对上面特性的反面,C++11也他讨论了如何防止子类再覆写父类的虚函数了——即父类的某些特性或方法被设计成不想再被改变。,他还会给你带来一个意想不到的收获:在C++11之前,关于子类覆写父类虚函数后,子类的虚函数还要不要加。之后,这个关键字本身就起了这个作用,之前的做法也就不是必须的了。

qq_27898413的博客 1537

Dify 深度进阶:从入门到精通AI应用构建

《Dify进阶指南:从基础到高阶的AI应用开发技巧》 本文深入探讨Dify平台的高级功能与应用开发技巧。首先强调核心概念的深化,包括提示词工程进阶、知识库高效管理和模型参数优化。重点介绍智能体应用的开发方法,如何让AI具备自主规划与执行能力。同时讲解工作流编排、前端集成等实用技术,并分享应用运营优化、成本控制等实践经验。文章还展望了Dify生态的发展趋势,为开发者提供从基础到进阶的完整学习路径,助力打造更智能高效的AI应用。

同名公众号:dify实验室 1950

【C++:多态】深入剖析C++多态精髓:虚函数机制、重写规范与现代C++多态控制

多态是一个继承关系的下的类对象,去调用同一函数,产生了不同的行为。比如Student继承了Person。Person对象买票全价,Student对象优惠买票。2.7。

艾莉丝努力练剑的博客,感谢大家一直以来的对艾莉丝的支持和鼓励,uu们如果对C/C++方向感兴趣的话,可以通过艾莉丝主页的微信名片加艾莉丝的好友,和努力练剑的艾莉丝一起加油! 1465

Arduino四足开源机器猫-Opencat(导航贴)

本内容由我和小伙伴共同编写,未经授权,谢绝转载! 1.项目介绍 2.组装调试教程 (一)Opencat-F (1)资料地址(包含程序代码&&3D打印图纸): 链接:https://pan.baidu.com/s/1AcVA5jSiOSzRKqEQAomrTA 提取码:szfx (2) (二)Opencat-B (1)组装调试 (2)组装调试注意事项 (3)添加新技能 3.编程 ...

qqliuzhitong的博客 4171

【3d打印】基于Klipper的3d打印远程上位机系统(Qt),klipper源码,通信解析,打印参数设置

基于Klipper的3d打印远程上位机系统(Qt),klipper源码,通信解析,打印参数设置(启庞Kp3s V1)

共享IT之美,共创机器未来 3190

【C++11特性篇】C++11中の【override】【final】关键字——帮助用户检测是否重写

前言大家好吖,欢迎来到YY滴C++系列 ,热烈欢迎!本章主要内容面向接触过C++的老铁主要内容含:欢迎订阅YY滴C++专栏!更多干货持续更新!以下是传送门!

YYDsis的博客 582

通过薄膜太阳能电池中的热激电流量化陷阱状态

发光(led)和光伏(PV)器件的性能取决于光活性半导体材料的质量。由于材料杂质或在制造过程中在半导体材料中无意地引入缺陷,导致偏离最佳性能。这些缺陷被称为阱态,或简称“阱”,它是半导体带隙内的高能态。一般来说,陷阱的发生可能是由于材料杂质(在散装)或在不同材料层之间的界面。在有机半导体中,由于天然物质的无序性,阱态经常出现。在无机和杂化半导体(例如:杂化钙钛矿、硅……)中,晶界的存在是圈闭态的已知来源。由于陷阱产生的原因多种多样,它们对装置的影响也是多种多样的。由于载流子的多次捕获和释放事件,特别是浅阱的

FluximAG的博客 2849

C++11核心特性实战:类默认函数、final/override、可变模板与emplace

在C++编程中,资源管理和代码安全是核心挑战。C++11通过引入移动语义和右值引用,从根本上优化了对象资源转移的效率,解决了深拷贝带来的性能瓶颈。这一特性与类默认函数的显式控制(=default/=delete)相结合,强化了RAII(资源获取即初始化)原则,使资源管理更加安全直观。在实际开发中,特别是在构建高性能库和并发组件时,这些特性能够显著提升代码的健壮性和执行效率。例如,通过可变参数模板实现类型安全的泛型容器,结合完美转发技术,可以构建灵活且高效的任务队列等基础组件。同时,override和fina

weixin_34321753的博客 1077

实战指南:R语言多组倾向评分逆概率加权(IPTW)建模与结果解读

本文详细介绍了如何使用R语言实现多组倾向评分逆概率加权(IPTW)建模,包括数据预处理、倾向评分估计、权重计算及结果解读。通过实战案例演示,帮助研究者掌握这一统计方法,有效平衡多组混杂因素,模拟随机对照试验效果,适用于医学、社会科学等领域的数据分析。

weixin_42528534的博客 395

《LabVIEW ZYNQ实战指南》第6章>>案例解析:基于USB WiFi模块的ZYNQ无线通信部署与Linux RT开发

本文详细解析了如何为ZYNQ开发板部署USB WiFi模块,实现无线通信。通过LabVIEW Linux RT环境,利用封装好的驱动VI,开发者可轻松配置ZYNQ连接无线网络,摆脱网线束缚,实现程序无线部署与远程调试,为工业物联网应用奠定基础。

app77的博客 754

《C++进阶之继承多态》【多态:概念 + 实现 + 拓展 + 原理】

本文介绍了C++中多态的概念、实现方式和底层原理。文章分为静态多态和动态多态两类。重点讲解了动态多态的实现条件:继承关系、虚函数重写和基类指针/引用调用。还介绍了协变返回类型等细节,并分析了虚函数表的多态实现机制。全文系统梳理了多态从概念到实践的关键知识点。

序属秋秋秋的博客 1401

Android 14.0 开机广播接收慢广播延迟问题分析

在14.0的系统rom定制化开发中,在某些时候在接收开机广播的时候发现,过了一分钟才收到开机广播,接下来就需要分析下 开机广播的相关分发机制,来处理下相关问题

安卓兼职framework和app工程师的博客 629

【C++】一文简练总结【多态】及其底层原理&具体应用(21)

本文主要介绍了多态的概念,多态的实现(虚函数,虚函数表,虚函数的重写,多态的构成,虚函数重写的两种特殊情况),c++11中引入的两个关键字【override】【final】从两个角度帮助用户检查是否重写,多态的具体应用:抽象类(又名,接口类,纯虚函数类)

YYDsis的博客 1098

uniapp(全端兼容) - 最新实现全局设置修改字体大小功能,统一修改全部页面所有文字的大小(标准/小/中/大字号切换,用户修改后整个项目立即生效)设置应用字体的大小(详细示例代码,一键复制开箱即用

uni-app,支付宝微信小程序,h5,手机端H5网页,移动端网站,安卓app,ios苹果app,nvue,全局设置字体大小,退出后还能记录字号大小,uniApp将全部页面的字体字号进行修改,uniapp设置改变全局字体大小功能,动态调整应用字体大小,详细教程,动态显示字体大小,可调整显示字体大小,如何解决不同端字体大小的问题,除了rem还有什么方法,设置全局字体大小,用户设置字号大小,应用字体大中小,实现用户全局设置应用字体大小,老年模式字体设置思路,h5端不行怎么办,移动端不生效,修改全局字号,一键改整

✅ 精选推荐文章 8884

ET2012新版免狗免安装绿色软件

ET2012新版免狗免安祖昂绿色软件。,新界面,新视觉,

深入理解C++面向对象特性之一 多态

C++ 的重点之一,多态,也是面向对象的三大特性之一。

2301_81978155的博客 1265

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

C++ 多态 纯干货讲解 复制可调试(1)

从上面可以看出,C++对函数重写的要求比较严格,但是有些情况下由于疏忽,可能会导致函数名字母次序写反而无法构成重载,而这种错误在编译期间是不会报出的,只有在程序运行时没有得到预期结果才来debug会得不偿失,因此:C++11提供了override和final两个关键字,可以帮助用户检测是否重写。调用指针或者引用指向的对象,指向父类就调用父类,指向子类就调用子类。查找虚函数表中存储的地址,是父类就调用父类,是子类就切割成父类对象。答案是B,容易选成D,需要注意的是,虚函数继承声明,重写实现。

陈大大陈的博客 408

RU DOS版、EFI版32bit/64bit BIOS常用工具

DOS版、EFI版的RU工具,可用来查看IO、MEM、PCI、SMBIOS等资源。亲测在EFI SHELL下可用。

廖雪峰Python教程[完整版]

廖雪峰的python教程整理的,已经排好版了,而且还是完整的,适合小白

override和final

C++干货系列——override和final详解 - 知乎

Ftworld21的专栏 147

C++必学系列:多态

多态是C++面向对象三大核心特性之一,也是面试高频考点。本文从生活实例切入,区分静态多态与动态多态,完整讲解运行时多态的两大实现条件、虚函数、虚函数重写、协变、虚析构、override/final关键字;对比重载、重写、隐藏三大易混淆概念;介绍纯虚函数与抽象类的接口设计思想;深入底层剖析虚函数表、虚指针、静态绑定与动态绑定原理,并搭配经典例题避坑。全文配套可运行代码,兼顾理论与实战,适合零基础入门、面试突击,看完彻底吃透C++多态底层逻辑。

2402_88116943的博客 655

【C++干货铺】继承后的多态 | 抽象类

本片文章介绍了继继承之后的多态和抽象类!!!

qq_55119554的博客 419

虚函数 静态联编 动态联编

参考文章:c++虚函数详解_Allwen_Tech的博客-优快云博客C++干货系列——override和final详解 - 知乎

cyy1104的博客 102
上一篇: C++干货系列——从编译期常量谈到constexpr(三)
下一篇: C++干货系列——C++17新特性之std::optional
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值