1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 class Base {public : int baseValue; virtual void baseFun () {} }; class B {public : int bValue; virtual void fb1 () {} virtual void fb2 () {} virtual void test () { cout << "B::test()" << endl; } }; class C : public Base {public : int cValue; virtual void fc1 () {} virtual void fc2 () {} virtual void test () { cout << "C::test()" << endl; } }; class A : public B, public C{ public : int aPublicValue; virtual void fb2 () {} virtual void fc2 () {} virtual void funcA1 () {} virtual void funcA2 () {} void funcA3 () {} private : short aPrivateValue; };
通过VisualStudio来查看A类的内存布局:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 class A size(28): +--- 0 | +--- (base class B) 0 | | {vfptr} 4 | | bValue | +--- 8 | +--- (base class C) 8 | | +--- (base class Base) 8 | | | {vfptr} 12 | | | baseValue | | +--- 16 | | cValue | +--- 20 | aPublicValue 24 | aPrivateValue | <alignment member> (size=2) +--- A::$vftable@B@: | &A_meta | 0 0 | &B::fb1 1 | &A::fb2 2 | &B::test 3 | &A::funcA1 4 | &A::funcA2 A::$vftable@C@: | -8 0 | &Base::baseFun 1 | &C::fc1 2 | &A::fc2 3 | &C::test A::fb2 this adjustor: 0 A::fc2 this adjustor: 8 A::funcA1 this adjustor: 0 A::funcA2 this adjustor: 0
从A::$vftable@B@
可以看出虚函数表中A和B合并在一起了,并且A::fb2
覆盖了B::fb2
。而C类的vftable则是独立存储。
B和C两个类都定义了test()函数,而A类则没有覆盖test方法,如果A类调用test方法到底采用的是那个呢?在A::$vftable@B@
指向的是B::test
。而A::$vftable@C@
指向的是C::test
。我们通过下面代码进行测试:
1 2 3 4 5 6 7 8 9 10 int main () { A a; a.B::test (); B& b = static_cast <B&>(a); b.test (); C* c = static_cast <C*>(&a); c->test (); return 0 ; }
所以基于A类的实例是无法调用test函数的,但是把A类转换成B/C的引用或者指针后,就能调用test方法了,但只能分别调用自己的函数实现。
我们现在把C继承Base使用virtual进行修饰,代码改动如下:
1 class C : public virtual Base
A类的内存布局情况如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 class A size(36): +--- 0 | +--- (base class B) 0 | | {vfptr} 4 | | bValue | +--- 8 | +--- (base class C) 8 | | {vfptr} 12 | | {vbptr} 16 | | cValue | +--- 20 | aPublicValue 24 | aPrivateValue | <alignment member> (size=2) +--- +--- (virtual base Base) 28 | {vfptr} 32 | baseValue +--- A::$vftable@: | &A_meta | 0 0 | &B::fb1 1 | &A::fb2 2 | &B::test 3 | &A::funcA1 4 | &A::funcA2 A::$vftable@C@: | -8 0 | &C::fc1 1 | &A::fc2 2 | &C::test A::$vbtable@: 0 | -4 1 | 16 (Ad(C+4)Base) A::$vftable@Base@: | -28 0 | &Base::baseFun A::fb2 this adjustor: 0 A::fc2 this adjustor: 8 A::funcA1 this adjustor: 0 A::funcA2 this adjustor: 0 vbi: class offset o.vbptr o.vbte fVtorDisp Base 28 12 4 0
可以看出class c中多了一个{vbptr}指针,这个叫做虚基类指针。
查看类内存布局的方法 使用VisualStudio 在VisualStudio中:工具 > 命令行 > 开发者命令行提示,然后输入命令:
1 cl /d1 reportAllClassLayout xxx.cpp > class.txt
输入内容比较多,搜索“class ClassName”就可以找到对应类的内部布局信息。
使用clang 1 2 clang -Xclang -fdump-record-layouts -stdlib=libc++ -std=gnu++11 xxx.cpp clang -Xclang -fdump-vtable-layouts -stdlib=libc++ -std=gnu++11 -I/usr/include/c++/11 -I/usr/include/x86_64-linux-gnu/c++/11 -c xxx.cpp
这种方式好像在继承关系使用virtual修饰时没有
1 2 3 4 5 6 7 8 9 10 11 12 13 14 *** Dumping AST Record Layout 0 | class A 0 | class B (primary base) 0 | (B vtable pointer) 8 | int bValue 16 | class C (base) 16 | (C vtable pointer) 24 | int cValue 28 | int aPublicValue 32 | short aPrivateValue 36 | class Base (virtual base) 36 | int baseValue | [sizeof=40, dsize=40, align=8, | nvsize=34, nvalign=8]
参考 多个基类