C++ 中的虚函数表及虚函数执行原理详解
为了实现虚函数,C++使用了虚函数表来达到延迟绑定的目的。虚函数表在动态/延迟绑定行为中用于查询调用的函数。
尽管要描述清楚虚函数表的机制会多费点口舌,但其实其本身还是比较简单的。
首先,每个包含虚函数的类(或者继承自的类包含了虚函数)都有一个自己的虚函数表。这个表是一个在编译时确定的静态数组。虚函数表包含了指向每个虚函数的函数指针以供类对象调用。
其次,编译器还在基类中定义了一个隐藏指针,我们称为*__vptr,*__vptr是在类实例创建时自动设置的,以指向类的虚函数表。*__vptr是一个真正的指针,这和*this指针不同,*this指针实际是一个函数参数,使编译器来达到自引用的目的。
结果就是,每个类对象都会多分配一个指针的大小,并且*__vptr是被派生类继承的。
如果你不清楚这些组件是怎么配合运作的,看下面的例子:
classBase { public: virtualvoidfunction1(){}; virtualvoidfunction2(){}; }; classD1:publicBase { public: virtualvoidfunction1(){}; }; classD2:publicBase { public: virtualvoidfunction2(){}; };
因为这里有3个类,编译器会创建3个虚函数表。
然后编译器会在使用了虚函数的最上层基类中定义一个隐藏指针。尽管这个过程编译器会自动处理,但我们还是通过下面的例子来说明指针添加的位置:
classBase { public: FunctionPointer*__vptr; virtualvoidfunction1(){}; virtualvoidfunction2(){}; }; classD1:publicBase { public: virtualvoidfunction1(){}; }; classD2:publicBase { public: virtualvoidfunction2(){}; };
*__vptr在类对象创建的时候会设置成指向类的虚函数表。例如,类型Base被实例化的时候,*__vptr就指向Base的虚函数表。类型D1或者D2被实例化的时候,*__vptr就指向D1或者D2的虚函数表。
现在我们来看下虚函数表是怎么创建的。因为示例中每个类仅有2个虚函数,所以每个虚函数表会存放两个函数指针(分别指向function1()和function2())。
Base对象的虚函数表最简单。Base对象只能访问Base类型的成员,不能访问D1或者D2的函数。所以Base的虚函数表中的两个指针分别指向Base::function1()和Base::function2()。
D1的虚函数表稍复杂点,D1对象能够访问D1以及Base的成员。D1重写了function1(),但没有重写function2(),所以D1的虚函数表中的两个指针分别指向D1::function1()和Base::function2()。
D2的虚函数表同理D1,包含了分别指向Base::function1()和D2::function2()的指针。
考虑如果创建D1对象时会发生什么:
intmain() { D1d1; }
因为d1是D1类型对象,d1有它自己的*__vptr指向D1类型的虚函数表。
现在创建一个Base类型指针*dPtr指向d1:
intmain() { D1d1; Base*dPtr=&d1; return0; }
重点:
因为dPtr是Base类型指针,它只指向d1对象的Base类型部分(即,指向d1对象中的Base子对象),而*__vptr也在Base类型部分。所以dPtr可以访问Base类型部分中的*__vptr。同时,这里注意,dPtr->__vptr指向的是D1的虚拟函数表,这是在d1初始化时就确定的。所以结果,尽管dPtr是Base类型指针,但它能够访问D1的虚函数表。
因此,当有调用dPtr->function1()时,发生了什么?
intmain() { D1d1; Base*dPtr=&d1; dPtr->function1(); return0; }
首先,程序识别到function1()是一个虚函数。
其次,程序使用dPtr->__vptr获取到了D1的虚函数表。
然后,它在D1的虚函数表中寻找可以调用的function1()版本,这里是D1::function1()。
因此,dPtr->function1()实际调用了D1::function1()。
通过虚函数表,编译器和程序能够确定调用什么版本的虚函数,尽管使用的是指向/引用基类的指针或者引用。
调用虚函数会比调用非虚函数更慢,有以下几个原因:
- 必须使用*__vptr获取正确的虚函数。
- 必须建立虚函数表的索引来获取想要调用的函数。
- 调用找到的函数。
结果就是必须进行三次操作才能完成对函数的调用。但是对于现代计算机系统,这些额外操作增加的时间几乎可以忽略不计。
另外,每个使用虚函数表的类都有*__vptr指针,从而每个类对象都会多一个指针的空间。虚函数很强大,但是它确实产生了性能开销。
到此这篇关于C++中的虚函数表及虚函数执行原理详解的文章就介绍到这了,更多相关C++虚函数表内容请搜索毛票票以前的文章或继续浏览下面的相关文章希望大家以后多多支持毛票票!
声明:本文内容来源于网络,版权归原作者所有,内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:czq8825#qq.com(发邮件时,请将#更换为@)进行举报,并提供相关证据,一经查实,本站将立刻删除涉嫌侵权内容。