Index C++
c++ 多态
多态是在父类函数的前面加上 “virtual” 关键字,使子类与父类同名的函数产生一种联系;
多态的核心是动态绑定(晚绑定),程序运行时根据对象的实际类型调用对应的成员函数,而非编译时根据指针/引用的声明类型绑定。其内部实现依赖两大关键机制:虚函数表(vtable)和虚指针(vptr),同时需要语言层面的virtual关键字触发该机制。
多态的基本用法
#include <iostream>
using namespace std;
class XYPos{...}; //x,y point
class Shape {
public:
Shape();
virtual ~Shape() {} // 析构函数建议设为虚函数(后续解释)
virtual void render();
void move(const XYPos&);
virtual void resize();
protected:
XYPos center;
};
class Ellipse : public Shape {
public:
Ellipse(float maj, float minr);
virtual void render(); //Ellipse 虚函数重写render
protected:
float major_axis, minor_axis;
};
void render(Shape* p) { p->render(); }
int main() {
Shape* ptr = new Ellipse(); // 基类指针指向派生类对象
Ellipse ell(10, 20);
ptr->reder(); // 运行时调用 Ellipse::render()(多态生效)
render(&ell); //运行时调用 Ellipse::render()(多态生效)
delete ptr; // 若析构是虚函数,会调用 Ellipse::~Ellipse()
return 0;
}
virtual写在函数前面的作用:如果使用指针或引用调用此函数时,只有在编译的时候才知道选哪个类的render函数去调用
析构函数必须是虚函数,解决基类指针析构派生类对象,如果~Shape析构不是虚函数那么上面代码中会有Ellipse对象的内存泄漏
构造函数绝对不能是虚函数,虚函数依赖vptr动态绑定,但构造函数的职责是初始化vptr,逻辑矛盾
多态的内部实现
所有的 有virtual的类的对象最头上都会又一个叫vptr的指针指向一个虚函数表(vtable);
这张vtable虚表里的内容是什么呢,是类的所有的有virtual的函数的地址(函数指针),这张表属于这个类,而不是类的每个对象,这就是说,每个类所有对象(实例)的vptr的值都是一样的,指向同一个地址;
下面两个图是内存模型:在每个类的虚函数表中发生继承的virtual函数会被替换成自己的,结合上边的代码;此时ell->render()时,ell指针的地址+2就是要调用的函数的地址了,所以说,虚函数的实现并不是去判断这个指针是什么类型,这样做的好处就是大大的提升效率。

C++ 编译器通过 vtable(虚函数表) 和 vptr(虚指针) 实现动态绑定,核心逻辑是:将虚函数的地址存储在表中,对象通过指针访问表,找到实际类型对应的函数地址。
1. 虚函数表(vtable):存储虚函数地址的 “全局数组”
- 每个包含虚函数的类(基类、派生类)都会被编译器生成一个唯一的 vtable(全局只读数据区);
- vtable 是一个函数指针数组,存储该类所有虚函数的地址(包括继承自基类但未重写的虚函数、自身重写的虚函数、新增的虚函数);
- 若派生类重写了基类的虚函数,会用派生类的函数地址覆盖 vtable 中对应位置的基类函数地址;
- 若派生类新增虚函数,会在 vtable 的末尾追加该函数地址。
示例:Shape 和 Ellipse 的 vtable 结构
| Shape 的 vtable | Ellipse 的 vtable |
|---|---|
| &Shape::~Shape() | &Ellipse::~Ellipse()(覆盖) |
| &Shape::render() | &Ellipse::render ()(覆盖) |
| &Shape::resize() | &Shape::resize() |
| (无其他虚函数) | (无新增虚函数) |
若 Ellipse 新增虚函数 virtual void func(),则其 vtable 会追加 &Ellipse::func()。
继承中的 vtable 继承与重写规则
- 派生类会继承基类的 vtable 结构(顺序一致);
- 重写的虚函数:覆盖 vtable 中对应位置的基类函数地址;
- 新增的虚函数:在 vtable 末尾追加(不同编译器可能有差异,但顺序稳定);
- 未重写的虚函数:vtable 中保留基类的函数地址(派生类对象调用时会执行基类版本)。
2. 虚指针(vptr):指向 vtable 的 “对象成员指针”
- 每个包含虚函数的对象(实例),会被编译器自动添加一个隐藏的成员变量 vptr(通常在对象内存布局的最开始位置);
- vptr 是一个指向当前对象所属类的 vtable 的指针;
- 对象创建时(构造函数执行阶段),vptr 会被初始化,指向自身类的 vtable(关键:构造函数中 vptr 已绑定当前类的表)。
3. 动态绑定的执行流程(核心步骤)
以 Shape* ptr = new Ellipse(); ptr->render(); 为例,拆解多态调用的底层过程:
- 编译阶段:编译器发现 render() 是虚函数,不会直接绑定函数地址,而是生成 “通过 vptr 访问 vtable 找函数地址” 的中间代码(而非硬编码 Shape::render() 的地址);
- 运行阶段:
- 访问指针 ptr 指向的对象(Ellipse实例)的隐藏成员 vptr;
- 通过 vptr 找到 Ellipse类的 vtable;
- 在 vtable 中找到 render() 对应的条目(已被 Ellipse重写,存储的是 &Ellipse::render());
- 调用该地址对应的函数,最终执行 Ellipse::render()。
本质:通过 vptr 间接访问 vtable,延迟到运行时确定函数地址,实现 “按对象实际类型调用”。
静态函数、友元函数不能是虚函数
- 静态函数属于类级别的函数(不依赖对象实例),而虚函数需要通过 vptr(对象成员)实现动态绑定,矛盾;
- 友元函数不属于类的成员函数,没有 this 指针,无法访问对象的 vptr,因此不能声明为虚函数。
考点
多态的原理是vtable(虚函数表)中被子类重写的虚函数会覆盖/修改vtable相应函数的函数指针地址,vptr(虚函数指针)每个类在构造的时候会在每个对象中存储一个指向其虚函数表的虚指针
只有通过指针或引用的方式才能去调用 ,通过对象 点 的方式是不会触发多态机制的
浙公网安备 33010602011771号