c++中虚函数的实现详解
前言
c++分为编译时多态和运行时多态。运行时多态依赖于虚函数,大部分人或许听说过虚函数是由虚函数表+虚函数指针实现的,但,真的是这样吗?虽然c++规范有着复杂的语言细节,但底层实现机制却任由编译器厂商想象。(没准某种特殊的处理器电路结构原生支持虚函数,没准这个处理器压根不是冯纽曼型,或者将来厂商发明了比虚函数表更有效率的数据结构。)
虚函数表
封装把实例的数据和操作结合在了一起,但实例本身只有数据,没有函数,同一个类的函数是共享的。我们通过一个例子来间接证明这一点
classBase1 { public: inta; voidfunc(){cout<<"heel"<<endl;} }; Base1b1; cout<<sizeof(b1)<<endl;
打印
4
如果类中有虚函数,则会在对象中加入一个虚函数指针,该指针指向一个虚函数表,表中是各个虚函数的地址。
+--------++---------+ |pvtbl|------>|vfunc1| +--------++---------+ |data1||vfunc2| +--------++---------+ |...||...|
当子类继承父类时,会依次覆盖虚函数表中的各个项,如果子类没有重写某项,那该项就保留。当实例化对象后,虚函数指针就作为一个隐藏数据存在于实例中。如果通过父类指针调用普通成员函数,由于普通函数和类型绑定在一起,所以仍会调用父类成员函数;如果通过父类指针调用虚函数,则会通过对象的虚指针找到虚函数表(即子类的虚函数表),定位虚函数项,实现多态。
原理是不是很简单?c++就是通过这种看似原始的方式实现高级抽象。以上是编译器的通用做法,我手上的VisualStudio2013编译器就是这么做的,为了提高性能,VS保证虚函数指针存在于对象实例中最前面位置(历史上也有编译器不这么做,好像是Borland的?)。
VisualStudio2013中的实现
来一个例子(能这么写是因为我已知了VisualStudio2013编译后对象的内存布局)
#include<iostream> usingnamespacestd; classBase { public: typedefvoid(*func)(); virtualvoidfunc1(){cout<<"Base::func1"<<endl;} virtualvoidfunc2(){cout<<"Base::func2"<<endl;} virtualvoidfunc3(){cout<<"Base::func3"<<endl;} }; classDerived:publicBase { public: virtualvoidfunc1(){cout<<"Derived::func1"<<endl;} virtualvoidfunc3(){cout<<"Derived::func3"<<endl;} }; intmain() { Baseb,b1; int**pvirtualtable1=(int**)&b; cout<<"Baseobjectvtbladdress:"<<pvirtualtable1[0]<<endl; int**pvirtualtable11=(int**)&b1; cout<<"anotherBaseobjectvtbladdress:"<<pvirtualtable11[0]<<endl; cout<<"functioninvirtualtable"<<endl; for(inti=0;(Base::func)pvirtualtable1[0][i]!=NULL;++i) { autop=(Base::func)pvirtualtable1[0][i]; p(); } cout<<endl; Derivedd; int**pvirtualtable2=(int**)&d; cout<<"Derivedobjectvtbladdress:"<<pvirtualtable2[0]<<endl; cout<<"functioninvirtualtable"<<endl; for(inti=0;(Base::func)pvirtualtable2[0][i]!=NULL;++i) { autop=(Base::func)pvirtualtable2[0][i]; p(); } cout<<endl; }
打印
Baseobjectpvtbladdress:0029DA58 anotherBaseobjectpvtbladdress:0029DA58 functionaddressinvirtualtable Base::func1 Base::func2 Base::func3 Derivedobjectpvtbladdress:0029DB20 functionaddressinvirtualtable Derived::func1 Base::func2 Derived::func3
可以看到,同一类型不同实例的虚函数表是相同的,继承之后,子类有了自己的虚函数表,表也有相应的更新(Derived::func1,Derived::func3),表中未重写的项还保留为原值(Base::func2)。
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流。