3.1 什么是多态

指同一个接口(函数)作用于不同的对象时,表现出不同的行为。

类型

说明

举例

编译时多态

编译阶段决定调用哪个函数

函数重载、模板、运算符重载

运行时多态

运行阶段决定调用哪个函数

虚函数 + 继承 + 指针/引用

问题

简洁回答

什么是多态?

同一个函数接口,作用于不同类型的对象时,产生不同的行为。

C++ 多态有哪几种?

编译时多态和运行时多态。

如何实现运行时多态?

使用继承、虚函数和基类指针或引用。

虚函数怎么工作的?

每个类有虚函数表,对象有虚指针,调用时查表。

构造函数中能多态吗?

不行。构造和析构中虚函数不会产生多态行为。

为什么析构函数要是虚的?

防止用基类指针删除派生类对象时资源泄露。

override 的作用?

显式标明重写父类虚函数,避免误写函数名或签名错误。

多态对性能有影响吗?

有轻微开销(查表一次),但大多数场景可忽略。

3.2 动态多态

✅ 条件:

  1. 基类中的函数必须是虚函数virtual 关键字)

  2. 子类必须重写这个虚函数

  3. 必须通过基类的指针或引用来调用

✅ 示例代码

#include <iostream>
using namespace std;

class Animal {
public:
    virtual void speak() const {
        cout << "Animal speaks" << endl;
    }
};

class Dog : public Animal {
public:
    void speak() const override {
        cout << "Dog barks" << endl;
    }
};

void makeSound(const Animal& a) {
    a.speak();  // 运行时决定调用哪个 speak
}

int main() {
    Dog d;
    makeSound(d);  // 输出:Dog barks
}

3.3 多态的底层机制

在 C++ 中,运行时多态依赖于虚函数机制,它能让你在运行时根据对象的真实类型调用正确的函数。

实现的关键就是两个隐藏机制:

  • 虚函数表(vtable)

  • 虚指针(vptr)

📌 1. vtable(虚函数表)

  • 每个定义了虚函数的类,编译器都会生成一张“虚函数表”(vtable)。

  • 这张表是一个函数指针数组,表中记录的是该类虚函数的地址

  • 如果类中有继承关系,则子类会继承并可能重写(覆盖)父类的虚函数地址

📌 2. vptr(虚指针)

  • 每个含有虚函数的对象,内部会自动带有一个隐藏的指针变量 vptr

  • vptr 指向该对象所属类的 vtable。

  • 编译器会在对象构造时自动初始化 vptr,在派生类构造阶段会重新指向正确的 vtable。

示例:

class Base {
public:
    virtual void show() { cout << "Base\n"; }
};

class Derived : public Base {
public:
    void show() override { cout << "Derived\n"; }
};

Base* b = new Derived();
b->show();  // 输出:Derived

调用过程如下:

(1)b 是指向 Derived 对象的 Base*

(2)b->show() 实际会变成:

 b->vptr->vtable[0]()

(3)因为 vptr 指向的是 Derived 的 vtable,所以调用的是 Derived::show

🔍vtable 的构造细节

  • 编译器为每个类创建 vtable,是一个只读的全局数组。

  • 如果类没有虚函数,则没有 vtable。

  • 如果类继承了虚函数但没有重写,vtable 中该位置仍指向父类函数。

  • 一旦重写,vtable 中该项会被替换成子类重写版本。

🔍构造 & 析构中的特殊行为

构造函数中调用虚函数不会多态!

因为构造函数阶段,vptr 还没有被初始化为子类的 vtable。

析构函数必须设为 virtual! 否则使用 Base* 删除 Derived 对象时,只会调用 Base 析构,资源可能泄露。

Derived 对象:

[对象内存]

+---------+-------------------------+

| vptr → | Derived 的 vtable 地址 |

+---------+-------------------------+

| 成员1 | ... |

| 成员2 | ... |

+---------+-------------------------+

vtable 内容(函数指针数组):

[0] → Derived::show

[1] → Derived::destructor

3.4常见的面试问题

问题1:虚函数怎么实现多态?

答:通过 vtable + vptr 实现运行时查表。

问题2:一个对象能有多个 vptr 吗?

答:多重继承时可能有多个 vptr。

问题3:构造函数中调用虚函数能多态吗?

答:不能,此时 vptr 还指向当前构造类的 vtable。

问题4:为什么要用虚析构函数?

答:保证基类指针 delete 派生类对象时调用完整析构链。

问题5:虚函数增加后影响 ABI 吗?

答:是的,vtable 的结构可能变化,影响二进制兼容性。