C++中的多态实际上就是让父类指针拥有多种形态。

C++使用虚函数来实现多态。

虚函数的原理就是在对象创建的同时创建了一张虚表。并且把虚表的地址放到对象的最顶部。那么父类指针肯定就可以获取到这个虚表。如果调用虚函数,则会直接通过虚表调用。

假设我们有两个类,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class people {
public:
virtual void laugh() {
cout << "small laugh\n";
}

virtual void piss() {
cout << "I can piss\n";
}
};

class boy :public people {
public:
void laugh() {
cout << "big laugh\n";
}
};

int main()
{
people p;
boy b;

people* pp = &b;
pp->laugh();

}

people有两个virtual函数,boy类只overwrite了一个。

我们先看一下底层是怎样调用虚函数的:

0.png

我们可以看到虚函数的call用到了call [edx]而不是直接跳到一个固定的地方。这里就是间接跳转

edx里面存放的就是虚表的地址值,[edx]就是虚表里面第一个地址。所以b这个对象调用了虚表里面的第一个函数。

同样的,如果我们调用p对象的laugh()函数,也是调用p对象虚表里面的第一个位置。

所以有了第一个结论:

虚表中的函数地址个数与virtual函数的个数一样,并且与virtual函数的位置对应(比如我这个例子中laugh()对应虚表第一个函数地址, piss()对应虚表第二个位置)

那么我们继续深入,看一下bp这两个对象的虚表有什么区别:

因为对象最顶上的第一个4bytes存放的就是虚表的地址,我是再Stack中创建的对象,直接在栈中查看,如下:

1.png

得到p的虚表地址为00b59c5c; b的虚表地址为00b59d5c

p的虚表如下:

2.png

p的虚表为00b51505,00b5151e

b的虚表如下:

3.png

b的虚表为: 00b5150f, 00b5151e

我们可以看到pb的虚表都有两个值,分别对应laugh()piss()。因为boy类重写了laugh()函数,所以boy类的虚表中第一个地址是自己重写的laugh(),而people类的第一个地址是people类中的laugh()地址。

因为boy类没有重写piss()。所以boy类的第二个地址就是people类的piss()地址。

所以第二个结论就是:

如果子类没有overwrite父类的virtual函数,那么子类虚表中对应的地址与最近的父类的对应地址相同。

Conclusion:

virtual function的实现方法如下:

4.png