C++ 多态详解
1. 多态分类
1.1 编译时多态(静态多态)
包括函数重载和函数模板。
1.2 运行时多态(动态多态)
本文重点内容。
2. 动态多态的定义及实现
2.1 构成条件
多态是指在继承关系下的类对象,调用同一函数产生不同的行为。
2.1.1 必要条件
a. 必须通过基类指针或引用调用函数。 b. 被调用的函数必须是虚函数。
注:基类 virtual 关键字虽可省略,但不建议,且常为考点。
2.1.2 虚函数重写(Override)
基类中有一个跟派生类完全相同的虚函数(即返回值类型、函数名字、参数列表类型),也称为'三同'。
常规思路认为调用 test() 触发多态(参数类型相同即可),易误判为 A->1,实为陷阱。实际构成多态的函数签名如下:
因多数示例参数值也相等,常被忽略此细节,正确答案应为 B->1。
若为以下情况:此处为子类指针,不构成多态,仅为普通函数调用。
注意:当 B 指针传入 test 时,this 指针类型为 A*。
2.1.3 协变返回类型
派生类重写基类函数时,若基类虚函数返回基类指针/引用,派生类可返回派生类指针/引用。
协变实用性有限,不建议使用。
2.1.4 虚析构函数
若基类析构函数为虚函数,派生类析构函数无需显式加 virtual(编译器有特殊处理)。若基类未加 virtual,delete 基类指针指向派生类对象时,仅调用基类析构,导致派生类资源泄漏。因此建议基类析构设计为虚函数。
此处出现两次 A 析构是因为子类析构前会自动调用父类析构。
综上,建议将基类析构函数设计为虚函数。
2.1.5 override 与 final 关键字
派生类重写虚函数后可加 override 检查函数签名一致性;若不希望被进一步重写,可使用 final 修饰。
2.1.6 同名函数隐藏
涉及虚函数时需注意虚表指针(vptr)大小:32 位系统为 4 字节,64 位系统为 8 字节。
3. 纯虚函数与抽象类
在虚函数后面加上=0,不需要定义实现,只要声明即可。包含虚函数的类叫做抽象类,抽象类不能实例化出对象。
4. 虚函数表机制
- 基类虚函数表存储所有虚函数地址。
- 派生类对象包含基类部分和自身成员。通常基类部分含虚表指针,派生类不重复生成。注意继承部分的虚表指针与基类对象独立。
- 派生类重写虚函数时,虚表中对应地址被更新为派生类函数地址。
- 派生类虚表包含继承的虚函数地址、重写后的地址及新增虚函数地址。
- 虚函数表本质是指向虚函数指针的数组。
- 虚函数与普通函数均编译为指令存入代码段,区别在于虚函数地址额外记录于虚表中。
- C++ 标准未严格规定虚函数表的具体存储位置。
5. 静态绑定与动态绑定
a. 不满足多态条件的调用在编译期绑定,确定函数地址,称为静态绑定。 b. 满足多态条件的调用在运行期绑定,通过虚表查找地址,称为动态绑定。

