C++多态的实现
最近去某大型互联网公司面试,对基础编码能力比较注重,问了一些基础性的问题,于是乎被鄙视了,这些基础知识之前也有过了解,但没有认真深究过,也在网上找了一些资料去看,没有太多实质性的收获,想把自己发现的问题及了解到的知识总结下,有空的话还是需要去看GNU gcc编译器的多态实现了。在这不再解释继承、覆盖、虚函数的基本概念了。
直接切入主题,C++多态的实现—虚表:
存在虚函数的类至少有一个(多继承会有多个)一维的虚函数表叫做虚表(virtual table)。编译器另外还为每个类的对象提供一个虚表指针(vptr),指向虚表开始位置。注意:类的实例化对象是没有虚表的,只有一个虚指针,在对象实例化时进行虚表的创建和虚指针的初始化。在实例化子类对象时,先实例化父类,此时编译器判断vptr=NULL创建虚表,把父类的所有虚函数地址加入到虚表中,并为虚指针赋值,当实例化子类对象时,重新为虚表中的虚函数指针进行赋值,子类存在的虚函数则得到新的指针地址,没有的则使用的是父类的虚函数指针地址。注意这里说的是实例化而不是构造函数,毕竟自定义构造函数是我们实现的,我们并没有添加这块代码,这么说更恰当。
以上是C++多态的实现过程,可以得出结论:
1、有虚函数的类必存在一个虚表。
2、当父类有虚函数,子类没有重写虚函数时,那么子类对象也会有一个虚指针,虚表中仍然会有该虚函数的地址,该地址指向的是父类的虚函数,如果实现了部分虚函数,则在实例化子类时,这些虚函数将得到重新赋值。
3、子类的虚表中虚函数地址的排列顺序和父类的虚表中虚函数地址排列顺序相同(为了后面的寻址方便)。
示意代码及示意图如下:
class Person{
. . .
public :
Person (){}
virtual ~Person (){};
virtual void speak (){};
virtual void eat (){};
};
class Girl : public Person{
. . .
public :
Girl(){}
virtual ~Girl(){};
virtual void speak(){};
virtual void sing(){};
};
经过以上的介绍,我们弄明白虚表的创建过程、虚指针的指向是怎么确定后,多继承的方式在这里不再做详细介绍了,大同小异,继承几个有虚函数的类就有几个虚表,该对象就有几个虚表指针,下面详细说下在运行期间是如何寻址的,如何找到调用函数的地址的。
虚函数表中有序放置了父类和子类中的所有虚函数,并且相同虚函数在类继承链中的每一个虚函数表中的偏移量都是一致的。所以确定的虚函数对应virtual table中一个固定位置n,n是一个在编译时期就确定的常量,所以,使用vptr加上对应的n,就可以得到对应的函数入口地址。C++采用的这种绝对地址+偏移量的方法调用虚函数,查找速度快执行效率高,时间复杂度为O(1)。
这里概括一下虚函数的寻址过程:
1、获取类型名和函数名
2、从符号表中获得当前虚函数的偏移量
3、利用偏移量得到虚函数的访问地址,并调用虚函数。vptr[n](param);
OK到这里应该就能把C++多态的实现原理讲明白了,如果想看编译器的源码请产考gnu的源码。以上的内容有很多都来自于互联网,盗取了不少其他作者的心血,当然也有一部分是经过自己的理解加工过的,如有不恰当的地方还望指出,欢迎各位交流。