大家都知道C++中的虚函数的实现一般是通过虚函数表(C++规范并没有规定具体用哪种方法,但大部分的编译器厂商都选择此方法),下面通过虚函数表来看看C++中虚函数的实现
class A
{
public:
int ai;
virtual void func(){cout<<"A-func"<<endl;}
};
class AA:public A
{
public:
void func(){cout<<"AA-func"<<endl;}
};
int main( )
{
AA *paa = new AA;
A *pa = new A;
}
可以看到paa指向的AA对象中,其子对象A的虚函数表(vftb)地址为0x0047e69c,正好是AA对象的首32位
倘若将类定义改成:
class A
{
public:
int ai;
virtual void func(){cout<<"A-func"<<endl;}
};
class AA:public A
{
public:
//void func(){cout<<"AA-func"<<endl;} 去掉AA中这个虚函数的override.
};
那么可以看到paa中的虚函数表中的第一个项(即从A继承而来的func),和pa中虚函数表的第一个项(A中的func),都指向同一个地方(0x004010f5),可以得出这样一个结论:当派生类没有覆盖(override)基类中的虚函数时,那么这个虚函数就会指向基类的虚函数实现。在C#中也有类似的概念,C#中的override关键字即表明派生类需要改写基类中虚函数项的指向。
在派生类中新增一个virtual func 虚函数
class A
{
public:
int ai;
virtual void func(){cout<<"A-func"<<endl;}
virtual void func2(){cout<<"A-func2"<<endl;}
};
class AA:public A
{
public:
void func(){cout<<"AA-func"<<endl;}
virtual void aa_func(){cout<<"AA-aa_func"<<endl;}
};
对于这份代码,VS2005的debugger并没有正确反映出AA中aa_func的位置(从图中看到vfptr虚函数表只有2项),但是,从图上仍然可以看出paa->aa_func占用了paa->__vfptr表中第三项位置(vfptr+8即表的第三项,每个表项的偏移为4个字节)。另外,我们再次看到如果派生类没有覆盖基类的虚函数,那么,它就和基类指向同一个函数体(两者的func2都指向了A::func2)
让我们回忆一下上一篇关于C++的类型转换的过程:
AA *paa = new AA;
这时候paa指向了AA的一个实例(instance)
A *pa = new AA;
这时候pa指向的仍然是AA的这个实例
因此,pa->func 和 paa->func都指向了AA实例中vftb的第一项,也就是AA:func(),为什么指针可以呈现出多态的原因也就不言而喻了。(对于引用也是同样的道理)
再来看看非虚函数和虚函数的汇编代码:
class A
{
public:
int ai;
virtual void func(){cout<<"A-func"<<endl;} //看看有(没有)virtual的汇编代码
};
class AA:public A
{
public:
};
非虚函数:
paa->A::func();
004010C6 mov ecx,dword ptr [paa]
004010C9 add ecx,4 //将paa对象压栈(偏移4是因为首4个字节保存的是vftb虚函数表)
004010CC call A::func (401046h)
虚函数:
paa->func();
00401230 mov edx,dword ptr [paa] //paa对象->edx
00401233 mov eax,dword ptr [edx] //vftb->eax
00401235 mov ecx,dword ptr [paa] //paa对象->ecx
00401238 mov edx,dword ptr [eax] //vftb的第一项->edx,也就是func所在的位置
0040123A call edx
经过一系列”复杂”的计算,最后edx寄存器里存放的就是func的正确地址,这也叫做late-binding(迟绑定)
最后看看虚函数导致对象的内存布局
当基类中没有虚函数的时候,自然基类也就没有虚函数表,而当基类中有虚函数的时候,编译器产生一个虚函数表,派生类就使用其基类子对象中的虚函数表.
class A
{
public:
int ai;
virtual void func2(){cout<<"A-func2"<<endl;}
};
class AA:public A
{
public:
void func(){cout<<"AA-func"<<endl;}
virtual void aa_func(){cout<<"AA-aa_func"<<endl;}
};
Vftb的第一项是A中的func2(),而第二项是AA中的aa_func
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步