07 - 虚函数及其实现原理,override关键字

在子类中,我们需要用到虚函数和override关键字来对父类的函数进行重写操作,顺便提到了虚函数的实现原理。

介绍

虚函数就是面向对象的第三大特点:多态。以上篇文章为例,在父类Spear中定义一个开火的虚函数,让两个子类去重写他,最后通过以该函数指针为参数的射击函数统一调用即可。

以下代码是使用虚函数virtual和关键字override的一个简单示例:

class Spear
{
public:
    // ...
    virtual void openFire() const
    {
        // ...
    }
private:
    // ...
};

class IceSpear : public Spear
{
public:
    // ...
    virtual void openFire() const override
    {
        // ...
    }
private:
    // ...
}

一些注意点

override关键字

当父类指针指向子类对象,且子类重写父类某一函数时。父类指针调用该函数,就会产生以下的可能 :

  • 该函数为虚函数:父类指针调用的是子类的成员函数。
  • 该函数不是虚函数:父类指针调用的是父类的成员函数。

因此,最好在子类重写的函数上标识overridevirtual可选。

析构函数

析构函数必须是虚函数:如果未声明成virtual,很容易在销毁对象时不释放内存。如果希望它什么也不做,可以显式设置=default

virtual ~Test() = default;

禁用重写

在函数后标注final关键字可以禁止子类重写这个方法。

动态绑定与静态绑定

静态绑定

静态绑定,也被称为早绑定。C++在编译类时,会创建一个包含类中所有方法的二进制对象。在非虚情况下,程序在编译时就确定了函数的地址。

动态绑定

程序在编译时确定的是其寻找函数地址的方法,只有在程序运行时才能真正确定函数的地址。

每个有虚函数的类都会有张虚表,对象其实就是指向虚函数表的指针,编译时编译器只告诉了程序会在运行时查找虚函数表的对应函数。

例如Base类和它的子类Derived:

纯虚函数与虚基类

以上边的枪类为例,基础的枪类有对应的对象吗?没有。它唯一的作用就是被子类继承。 因此,可以利用纯虚函数将此类变成虚基类。虚基类会被认为其无实际意义,无法产生对象

class Spear
{
public:
    Spear(const std::string& name_, const std::string& icon_) :name(name_), icon(icon_) 
    {}
    virtual ~Spear() = default;
    // 纯虚函数
    virtual void openFire() const = 0;
protected:
    std::string name;
    std::string icon;
};

// 子类可以这样继承虚基类
class FireSpear() : public virtual Spear
{
    // ...
};

基类的openFire()实现过程没有意义,它就是用来被重写的。所以纯虚函数就此诞生,只要将一个虚函数写为纯虚函数,那么该类将被认为无实际意义的类,无法产生对象。纯虚函数也不用去写实际部分。写了编译器也会自动忽略。

纯虚函数的特点就是语法简单,却经常使用,必会。

参考资料

  • 飘零的落花 - 现代C++详解
  • C++20高级编程