C++ 虚表虚函数怎么就实现了多态?
虚表vftable,编译器为每个拥有虚函数的类都建有一张虚函数表,里面存有虚函数的入口指针(地址)。在类对象的内存布局中,先是一个vfptr虚表指针,指向虚表首地址,而后通过偏移量的形式来访问虚表中的地址。
看许多文章都在那里侃侃而谈,然能实际展示类(对象)内存布局者寥寥,不可见内里实现的终究是借他人文字的空想。now, 我们来一窥究竟!
PS:如何利用VS查看类内存布局见文末链接
1. 带虚函数类内存布局
2. 发生单继承时,派生类内存布局,先是复制一份基类内存布局,然后是自己的布局(注意内存对齐)。虚表指针指向自己的虚表,派生类虚函数地址如果自己未覆盖,那么就是基类的,否则是自己的函数地址。
3. 发生多继承时:先按照继承顺序,从左到右排布基类的布局包括虚标指针,然后排布自己的指针和数据;派生类虚表排布形式是按照继承顺序,是继承来的虚函数,如果有覆盖则换成自己的函数地址;然后是下一个基类,直至基类排布完毕。继承来的多张表是独立的(从内存布局中的多个虚表指针可以看出),且使用首地址+偏移量的形式来访问。
4. 发生虚继承时:无论是对象内存排布还是虚表,虚基类的部分都被放到最后排布,且如果派生类有自己的虚函数则会加在第一个基类的虚表末尾。
除vfptr和vftable之外,增加了vbtable 虚基类表(存放继承的虚基类的地址)和 vbptr(指向虚基类表的指针)
测试代码
1 #include <iostream> 2 class Base 3 { 4 int a; 5 int b; 6 public: 7 virtual void foo() {}; 8 virtual void bar() {}; 9 virtual void bar2(){}; 10 virtual void bar3(){}; 11 }; 12 13 class Base2 14 { 15 int d; 16 public: 17 virtual void foo() {}; 18 }; 19 20 class Derived : virtual public Base, public Base2 21 { 22 int c; 23 public: 24 //void foo() {}; 25 void bar() override {}; 26 virtual void bar4() {}; 27 }; 28 29 int main() 30 { 31 Base b; 32 Derived d; 33 return 0; 34 }
多态原理?个人理解
1. C++多态动态建立在虚函数上,使用virtual关键字指明不要在编译器绑定函数地址,而是在运行时访问虚函数表,即动态绑定。
2. 使用基类指针指向派生类对象且调用虚函数时(前提是派生类覆盖了基类虚函数),运行时该指针指向的地址是派生类对象地址,在派生类对象地址头就是指向虚函数表的vfptr,这张虚表自然是派生类的,而在构造虚表的过程中该虚函数早就被派生类自己的函数地址所覆盖,所以调用函数自然是派生类的函数。
3. 多态的含义:基类指针可以指向基类对象以及不同派生类对象,实现了一种写法,多种访问方式的效果,称为多态。
【参考资料】
如何利用VS查看类内存排布:https://www.cnblogs.com/jerry19880126/p/3616999.html
(在VS项目右键属性->C/C++->命令行->添加 /d1 reportAllClassLayout 应用即可,注意debug/release还有平台类型)
C++多态以及虚函数的不错文章:https://www.cnblogs.com/cxq0017/p/6074247.html