虚函数

这是C++中多态的一种呈现方式,为了更好的得以了解虚函数的本身的含义与相关意义。让我们先了解下,在C++还有那些实现多态的方案。

  • 重载。允许存在多个重名函数(C语言不支持该语法)
  • 覆盖。是指子类重新定义父类虚函数的做法,可通过父类的指针调用实际子类的成员函数。

虚函数表

  • Virtual Table是一块连续的内存,每个内存单元记录一个JMP指令的地址

  • 类别

    单继承无虚函数覆盖

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    class Base{
    public:
    virtual void func1();
    virtual void func2();
    virtual void func3();
    }

    class Derived : public Base{
    public:
    virtual void func4();
    virtual void func5();
    virtual void func6();
    }

    // 值得注意的点
    - 虚函数按照声明顺序依次放入表中
    - 父类的虚函数在子类的虚函数前面

    image-20221101211434362

单继承有虚函数覆盖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Base{
public:
virtual void func1();
virtual void func2();
virtual void func3();
}

class Derived: public Base{
public:
virtual void func1();
virtual void func5();
virtual void func6();
}

// 值得注意的点
- 覆盖的函数被放到了虚表中原父类虚函数的位置
- 没有覆盖的依次存放

image-20221101211514366

多继承无虚函数覆盖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Base1{
public:
virtual void func1();
virtual void func2();
virtual void func3();
}
class Base2{
public:
virtual void func1();
virtual void func2();
virtual void func3();
}
class Base3{
public:
virtual void func1();
virtual void func2();
virtual void func3();
}


class Derived: public Base1, Base2, Base3{
public:
virtual void func4();
virtual void func5();
virtual void func6();
}
// 值得注意的点
- 每个父类有自己的虚表
- 子类的成员函数被放到了第一个父类的表中

image-20221101211644766

多继承有虚函数覆盖

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
class Base1{
public:
virtual void func1();
virtual void func2();
virtual void func3();
}
class Base2{
public:
virtual void func1();
virtual void func2();
virtual void func3();
}
class Base3{
public:
virtual void func1();
virtual void func2();
virtual void func3();
}

class Derived: public Base1, Base2, Base3{
public:
virtual void func1();
virtual void func5();
virtual void func6();
}

// 值得注意的点
- 每个父类有自己的虚表,被覆盖的放在父类的原来位置
- 子类的成员函数被放到了第一个父类的表中

image-20221101212106062

  • 一些细节的补充,除了这些成员函数的内存分布外,还有两个额外的标志位

  • offset_of_top用于表示虚表指针距离类开头的距离

  • typeinfo for base 用于支持RTTI

虚函数常见问题

  • 开销
    • 带有虚函数的每个类产生一个虚函数表,用来存储虚成员函数的指针
    • 每个带有虚函数的类都会有一个指向虚函数表的指针(共享同一个虚表)
  • 不可以作为虚函数的函数
    • 构造函数:虚表和虚指针需要通过此函数来进行构造
    • 内联函数:内联函数需要在编译期完成替换,虚函数则是在运行期确定
    • 静态函数:静态函数属于类而非对象,没有this指针,无法访问虚表
    • 友元函数、普通函数
  • 纯虚函数、虚函数
    • 纯虚函数仅有声明
    • 虚函数既可声明,也可定义
  • 父类设置虚析构函数的意义
    • 避免内存泄漏,无法释放子类对象开辟的空间