C++学习笔记----9、发现继承的技巧(一)---- 使用继承构建类(4)

news/2024/10/17 23:51:29/

2.4、override关键字

        override关键字的使用是可选的,但强烈推荐。没有这个关键字,可能会意外在继承类中而不是重载基类中的成员函数生成一个新的(virtual)成员函数,而有效地隐藏了基类中的成员函数。看一下下面的Base与Derived类,Derived恰当地重载了someFunction(),但是却没有使用override关键字:

class Base
{
public:virtual void someFunction(double d);
};class Derived : public Base
{
public:virtual void someFunction(double d);
};

        可以通过引用调用someFunction()如下:

Derived myDerived;
Base& ref { myDerived };
ref.someFunction(1.1); // Calls Derived's version of someFunction()

        它正确地调用了Derived类的重载的someFunction()。现在,假设你在重载someFunction()时意外地使用了一个整型参数而不是double,如下:

class Derived : public Base
{
public:virtual void someFunction(int i);
};

        这个代码没有重载Base的someFunction(),但是生成了一个新的virtual成员函数。如果你想如下代码所示通过Base引用来尝试调用someFunction(),是Base的someFunction()被调用而不是Derived的!

Derived myDerived;
Base& ref { myDerived };
ref.someFunction(1.1); // Calls Base's version of someFunction()

        这种类型的问题会在你开始修改Base但是忘记了去修改所有的继承类的时候会发生。例如,可能你的Base类的第一个版本有一个叫做someFunction()接受整型数的成员函数。然后呢,你写了一个Derived类重载了这个接受整型数的someFunction()。后来你决定Base中的someFunction()需要一个double而不是整数,所以你更新了Base类中的someFunction()。当时这种情况是可能发生的,你忘记了去更新继承类中的someFunction()的重载,也去接受一个double而浊整数。由于把这个忘了,你现在实际上是生成了一个新的virtual成员函数而不是恰当地重载了基类的成员函数。

        可以通过使用override关键字如下来防止这种情况:

class Derived : public Base
{
public:void someFunction(int i) override;
};

        该Derived的定义生成一个编译错误,因为带上override关键字,你的意思就是someFunction()是要重载基类的一个成员函数,而基类没有someFunction()接受整数,只有一个接受double。

        这种问题意外地生成一个新的成员函数而不是恰当地重载一个也可能会发生在重命名基类中的成员函数而忘记重命名继承类中的重载成员函数。

        警告:在成员函数上总是要使用override关键字意味着要重载基类的成员函数。

2.5、virtual的真相

        到现在为止你知道了如果一个成员函数不是virtual,在继承类中尝试重载它就会隐藏那个成员函数的基类版本。本节为探索编译器是如何 实现virtual成员函数的,它们的性能影响是什么,也会讨论virtual析构函数的重要性。

2.5.1、virtual是如何实现的

        要理解如何避免成员函数隐藏,需要知道更多一点virtual关键字实际上干了什么。当类在c++中编译的时候,生成一个二进制文件对象,包含了类的所有的成员函数。在non-virtual情况下,代码将控制权转移给恰当的成员函数是直接在基于编译时类型的成员函数被调用时硬编码的。这叫做静态绑定,也叫做前绑定。

        如果成员函数被声明为virtual,正确的实现是通过使用一块叫做vtable,或”virtual table”的内存区域来调用的。每个类有一个或多个virtual成员函数有一个vtable,每一个这样的类对象都包含一个指向对应vtable的指针。该vtable包含指向virtual成员函数的实现的指针。用这种方式,当在一个对应对象的指针或引用上调用一个成员函数时,它的vtable指南就跟随着,恰当版本的成员函数基于运行时对象的实际类型执行。这叫做动态绑定,也叫做后绑定。记住动态绑定只在使用对象的指针或引用时有效是很重要的。如果在一个对象上直接调用一个virtual成员函数,那么该调用会使用编译时解析好了的静态绑定。

        为了更好的理解vtable是如何使得重载成员函数成为可能,看下面Base与Derived类的例子:

class Base
{
public:virtual void func1();virtual void func2();void nonVirtualFunc();
};class Derived : public Base
{
public:void func2() override;void nonVirtualFunc();
};

        在这个例子中,假定你有下面两个实例:

Base myBase;
Derived myDerived;

        下面的图展示了两个实例的vtable的高阶视图。myBase对象包含一个指向它的vtable的指针。该vtable有两个入口,一个是func1(),一个是func2()。这些入口指向了Base::func1()与Base::func2()的实现。

        myDerived也包含了一个指向它的vtable的指针,也有两个入口,一个是func1(),一个是func2()。它的func1()入口指向了Base::func1(),因为Derived没有重载func1()。另一方面,它的func2()入口指向了Derived::func2()。

        注意两个vtable都没有包含任何nonVirtualFunc()成员函数的入口,因为这个成员函数不是virtual。


http://www.ppmy.cn/news/1539844.html

相关文章

Docker-compose 单节点管理、consul 注册中心、registrator、template

consul是一个基于分布式的服务发现和配置管理工具。它具有快速构建分布式架构,提供服务发现和服务注册功能。consul职能:1、自动发现、注册;2、自动配置;3、自动更新 服务发现:自动检查网络中的服务(如数据…

【LeetCode】每日一题 2024_10_14 鸡蛋掉落(记忆化搜索)

前言 每天和你一起刷 LeetCode 每日一题~ LeetCode 启动! 题目:鸡蛋掉落 代码与解题思路 今天的题目是昨天的进阶版,昨天给了 2 个鸡蛋,让我们求在一栋有 n 层楼的建筑中扔鸡蛋的最大操作次数 但是今天的题目给了 k 个鸡蛋&am…

实现与PDF进行聊天!(利用 Pinata、OpenAI 和 Streamlit等技术)

最近在GitHub上发现一个有趣的项目,由用户 Jagroop2001开发的【chat-with-pdf】这个project! 我为此项目写了一个介绍和readme,感兴趣的可以点击链接: https://github.com/Hyone-soul/chat-with-pdf/ 在本教程中,我们…

【人工智能AIGC术语100条】Shelly聊AI-重磅发布!

大家好,我是Shelly,一个专注于输出AI工具和科技前沿内容的AI应用教练,体验过300款以上的AI应用工具。关注科技及大模型领域对社会的影响10年。关注我一起驾驭AI工具,拥抱AI时代的到来。 01 人工智能&AIGC术语100条全网发布 在…

Linux SSH无密码使用私钥远程登录连接详细配置流程

文章目录 前言1. Linux 生成SSH秘钥对2. 修改SSH服务配置文件3. 客户端秘钥文件设置4. 本地SSH私钥连接测试5. Linux安装Cpolar工具6. 配置SSHTCP公网地址7. 远程SSH私钥连接测试8. 固定SSH公网地址9. 固定SSH地址测试 前言 本文将详细介绍如何将Linux SSH服务与cpolar相结合&…

React JSX 使用条件语句渲染UI的两种写法

只针对函数组件 1. 第一种写法&#xff1a; function App({ id }) {return id1? <h1>hello</h1> : <h1>world</h1>; } 或者&#xff1a; function App({ id }) {return (<h1>{id1 && "hello" || id2 && "wo…

C++的随机数操作

首先想到的肯定是rand()函数&#xff0c;但是这个有点问题 引入头文件<stdlib.h> 如果不引入种子&#xff0c;它的随机数不是随机数&#xff0c;是固定的一串数字 srand()函数&#xff0c;产生随机的种子 示例&#xff1a; 产生0-99的随机数 #include<stdlib.h&g…

Java工具类--OkHttp工具类

以springboot项目举例 1.pom添加maven依赖 <dependency> <groupId>com.squareup.okhttp3</groupId> <artifactId>okhttp</artifactId> <version>你的OkHttp版本</version> </dependency> 2.创建OkHttp工具类&#xff…