当前位置: 首页 > article >正文

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

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.kler.cn/news/353870.html

相关文章:

  • 雷达手势识别技术
  • C++ IO多路复用 poll模型
  • 前端学习-CSS的三大特性(十七)
  • python 爬虫 入门 一、基础工具
  • 分布式数据库环境(HBase分布式数据库)的搭建与配置
  • [算法日常] 逆序对
  • 音乐播放器项目专栏介绍​
  • Linux的kafka安装部署
  • 自动化测试与敏捷开发的重要性
  • docker (desktopcompose) download
  • KVM 建黑群晖
  • HarmonyOS NEXT 应用开发实战(六、组件导航Navigation使用详解)
  • JavaWeb Servlet--09深入:注册系统03--删除用户业务
  • 使用WordPress从零开始搭建一个本地网站实现远程访问
  • [Python]将pdf文件转为svg
  • VMware16的安装及VMware配置Linux虚拟机
  • Windows模拟电脑假死之键盘鼠标无响应
  • 【Java后端】Spring vs SpringBoot
  • Spring Boot比Spring多哪些注解
  • extern与static