C++虚成员函数表vtable
通过前两节的介绍,想必对多态有一定的了解了,本节将介绍多态的底层实现机制。关于如何实现多态,对于程序设计人员来说即使不知道也是完全没有关系的,但是对于加深对多态的理解具有重要意义。
C++ 通过虚成员函数表 vtable 实现多态,虚函数表中存储的是类中虚函数的入口地址。普通类中是没有虚函数表的,只有在具有虚函数的类中(无论是自身添加的虚函数还是继承过来的虚函数)才会具有虚函数表。通常,虚函数表的首地址将会被存入对象的最前面(在 32 位的操作系统中,存储地址是用 4 个字节,因此这个首地址就会占用对象的前四个字节的空间)。
【例 1】
虚函数表里存储的就是虚函数的入口地址。我们再来看主函数,函数中先定义了 base 类对象 b,因为 b 类中有虚函数,因此存在虚函数表,而虚函数表的首地址就存储在对象所在存储空间的最前,具体情况可以见下图。当然声明 derived 对象 d 之后,情况也跟下图中一样,同样在对象存储空间中包含虚成员函数表地址。
之后定义了一个基类类型的指针 p,当通过 p 调用虚函数 v1 或 v2 时,系统会先去 p 所指向的对象的前四个字节中寻找到虚函数表地址,之后在内存中找到该虚函数表,然后在表中找到对应函数的入口地址,就可以访问这个函数。
当 p 指针指向的是基类对象时,基类的虚函数表将会被访问,基类中虚函数将会被调用。当 p 指针指向的是派生类对象时,访问的是派生类的虚函数表,派生类的虚函数表中存的是派生类中的虚函数入口地址,因此调用的是派生类中的虚函数。
使用多态会降低程序运行效率,使用多态的程序会使用更多的存储空间,存储虚函数表等内容,而且在调用函数时需要去虚函数表中查询函数入口地址,这会增加程序运行时间。在设计程序时,程序设计人员可以选择性的使用多态,对于有需要的函数使用多态,对于其它的函数则不要采用多态。
通常情况下,如果一个类需要作为基类,并且期望在派生类中修改某成员函数的功能,并且在使用类对象的时候会采用指针或引用的形式访问该函数,则将该函数声明为虚函数。
声明:《C++系列教程》为本站“54笨鸟”官方原创,由国家机构和地方版权局所签发的权威证书所保护。
C++ 通过虚成员函数表 vtable 实现多态,虚函数表中存储的是类中虚函数的入口地址。普通类中是没有虚函数表的,只有在具有虚函数的类中(无论是自身添加的虚函数还是继承过来的虚函数)才会具有虚函数表。通常,虚函数表的首地址将会被存入对象的最前面(在 32 位的操作系统中,存储地址是用 4 个字节,因此这个首地址就会占用对象的前四个字节的空间)。
【例 1】
#include<iostream> using namespace std; class base { public: virtual void v1(){ } virtual void v2(){ } }; class derived: public base { public: virtual void v1(){ } virtual void v2(){ } }; int main() { base b; derived d; base *p; p = &b; p->v1(); p->v2(); p = &d; p->v1(); p->v2(); return 0; }我们将两个类定义成例 1 所示形式,两个类中各有两个虚函数 v1 和 v2,我们将其函数入口地址找到列于下表中:
虚成员函数 | 函数入口地址 | 虚成员函数 | 函数入口地址 |
---|---|---|---|
base::v1 | 00D15834 | derived::v1 | 00D15844 |
base::v2 | 00D15838 | derived::v2 | 00D15848 |
虚函数表里存储的就是虚函数的入口地址。我们再来看主函数,函数中先定义了 base 类对象 b,因为 b 类中有虚函数,因此存在虚函数表,而虚函数表的首地址就存储在对象所在存储空间的最前,具体情况可以见下图。当然声明 derived 对象 d 之后,情况也跟下图中一样,同样在对象存储空间中包含虚成员函数表地址。

之后定义了一个基类类型的指针 p,当通过 p 调用虚函数 v1 或 v2 时,系统会先去 p 所指向的对象的前四个字节中寻找到虚函数表地址,之后在内存中找到该虚函数表,然后在表中找到对应函数的入口地址,就可以访问这个函数。
当 p 指针指向的是基类对象时,基类的虚函数表将会被访问,基类中虚函数将会被调用。当 p 指针指向的是派生类对象时,访问的是派生类的虚函数表,派生类的虚函数表中存的是派生类中的虚函数入口地址,因此调用的是派生类中的虚函数。
使用多态会降低程序运行效率,使用多态的程序会使用更多的存储空间,存储虚函数表等内容,而且在调用函数时需要去虚函数表中查询函数入口地址,这会增加程序运行时间。在设计程序时,程序设计人员可以选择性的使用多态,对于有需要的函数使用多态,对于其它的函数则不要采用多态。
通常情况下,如果一个类需要作为基类,并且期望在派生类中修改某成员函数的功能,并且在使用类对象的时候会采用指针或引用的形式访问该函数,则将该函数声明为虚函数。
声明:《C++系列教程》为本站“54笨鸟”官方原创,由国家机构和地方版权局所签发的权威证书所保护。