多态
父类指针指向一个子类对象,调用父类声明的方法后,实际上调用的是子类的实
1 | class A |
多态的实现原理(虚函数的实现原理)
多态是通过虚函数实现的,如果不声明 virtual 的情况下,无法正确调用到子类的实现:
编译器编译时需要确定每个对象调用的函数(非虚函数)的地址,这称为早期绑定,当子类对象的地址赋给父类指针后,C++ 编译器进行了类型转换,将子类对象当成父类处理,从而调用了父类的函数实现。
实现原理:虚函数表+虚表指针
每个类使用一个虚函数表,每个类对象用一个虚表指针。(注意对象和类的区别)
基类对象包含一个虚表指针,指向基类中的虚函数表。子类对象也将包含一个虚表指针,指向子类虚函数表
如果子类重写了基类的虚函数,该子类的虚函数表将保存重写的虚函数地址,而不是基类的虚函数地址
如果子类没有重写基类的虚函数,该子类的虚函数表将保存基类的虚函数地址。如果子类定义了新的虚函数,则该虚函数地址会被添加到子类的虚函数表中
内存布局:
- A 类的虚函数表(注意是类,而不是对象):
1 | A::f 的地址 |
- B 类的虚函数表(注意是类,而不是对象):
1 | A::f 的地址 |
- 如果执行
B b;
,b 的内存布局如下
1 | vptr: 指向 B 的虚表 vtableB // 虚表指针 |
- 如果执行
A *pA = new B();
pA 的内存布局同普通的 A 对象一致,除了虚函数指针,如下
1 | vptr: 指向 B 的虚表 vtableB // 虚表指针 |
这种情况下 pA 是无法访问到定义在 B 中的 b 对象的
执行 pA->g();
时,编译器知道的是,g 是一个声明为 virtual 的成员函数,而且其入口地址放在虚表的第二个(无论是A 的虚表还是B 的虚表),那么编译器就会去 B 的虚表中查找,得到了 B::g 的地址,从而实现了多态