C++多态

多态

父类指针指向一个子类对象,调用父类声明的方法后,实际上调用的是子类的实

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class A
{
public:
virtual void f(){};
virtual void g(){};
int a;
};

class B : public A
{
public:
void g(){};
int b;
};

A *pA = new B();
pA->g();

多态的实现原理(虚函数的实现原理)

多态是通过虚函数实现的,如果不声明 virtual 的情况下,无法正确调用到子类的实现:

编译器编译时需要确定每个对象调用的函数(非虚函数)的地址,这称为早期绑定,当子类对象的地址赋给父类指针后,C++ 编译器进行了类型转换,将子类对象当成父类处理,从而调用了父类的函数实现。

实现原理:虚函数表+虚表指针

每个类使用一个虚函数表,每个类对象用一个虚表指针。(注意对象和类的区别

基类对象包含一个虚表指针,指向基类中的虚函数表。子类对象也将包含一个虚表指针,指向子类虚函数表

  • 如果子类重写了基类的虚函数,该子类的虚函数表将保存重写的虚函数地址,而不是基类的虚函数地址

  • 如果子类没有重写基类的虚函数,该子类的虚函数表将保存基类的虚函数地址。如果子类定义了新的虚函数,则该虚函数地址会被添加到子类的虚函数表中

内存布局:

  • A 类的虚函数表(注意是类,而不是对象):
1
2
A::f 的地址
A::g 的地址
  • B 类的虚函数表(注意是类,而不是对象):
1
2
A::f 的地址
B::g 的地址 // 存放了重写的地址
  • 如果执行 B b;,b 的内存布局如下
1
2
3
vptr: 指向 B 的虚表 vtableB    // 虚表指针
int a: 继承 A 的成员 // 继承了 A,所以有 a
int b: B 自己的成员 // 自己的成员变量
  • 如果执行 A *pA = new B(); pA 的内存布局同普通的 A 对象一致,除了虚函数指针,如下
1
2
vptr: 指向 B 的虚表 vtableB    // 虚表指针
int a: A 的成员

这种情况下 pA 是无法访问到定义在 B 中的 b 对象的

执行 pA->g(); 时,编译器知道的是,g 是一个声明为 virtual 的成员函数,而且其入口地址放在虚表的第二个(无论是A 的虚表还是B 的虚表),那么编译器就会去 B 的虚表中查找,得到了 B::g 的地址,从而实现了多态

  1. 多态
  2. 多态的实现原理(虚函数的实现原理)