单虚继承

虚拟继承和继承的区别就是在语法上,在基类前添加virtual关键字

虚拟继承单基类内存布局

代码如下:

class B { public: int ib; char cb; public: B():ib(0),cb('B') {} virtual void f() { cout << "B::f()" << endl;} virtual void Bf() { cout << "B::Bf()" << endl;} }; class B1 : virtual public B { public: int ib1; char cb1; public: B1():ib1(11),cb1('1') {} virtual void f() { cout << "B1::f()" << endl;} virtual void f1() { cout << "B1::f1()" << endl;} virtual void Bf1() { cout << "B1::Bf1()" << endl;} }; typedef void(*Fun)(void); int main() { int** pVtab = NULL; Fun pFun = NULL; B1 bb1; pVtab = (int**)&bb1; cout << "[0] B1::_vptr->" << endl; pFun = (Fun)pVtab[0][0]; cout << " [0] "; pFun(); //B1::f(); cout << " [1] "; pFun = (Fun)pVtab[0][1]; pFun(); //B1::f1(); cout << " [2] "; pFun = (Fun)pVtab[0][2]; pFun(); cout << " [3] 0x"; pFun = (Fun)pVtab[0][3]; cout << pFun << endl; cout << "[1] B1::ib1 = "; cout << (int)*((int*)(&bb1)+1) <<endl; //B1::ib1 cout << "[2] B1::cb1 = "; cout << (char)*((int*)(&bb1)+2) << endl; //B1::cb1 cout << "[3] B::_vptr->" << endl; // cout << "基类f()地址: " << *(int*)*(int*)((int*)(&bb1)+3) << endl; // pFun = (Fun)*(int*)*(int*)((int*)(&bb1)+3); // pFun(); //B1::f(); 如果取消注释,运行到这里报error:Segmentation fault (核心已转储) pFun = (Fun)pVtab[3][0]; cout << " [0] "; pFun(); //B1::f(); pFun = (Fun)pVtab[3][1]; cout << " [1] "; pFun(); //B::Bf(); cout << " [2] "; cout << "0x" << (Fun)pVtab[3][2] << endl; cout << "[4] B::ib = "; cout << (int)*((int*)(&bb1)+4) <<endl; //B::ib cout << "[5] B::cb = "; cout << (char)*((int*)(&bb1)+5) <<endl; //B::cb cout << "[6] NULL = "; cout << (int)*((int*)(&bb1)+4) <<endl; return 0; }

结果:

[0] B1::_vptr-> [0] B1::f() [1] B1::f1() [2] B1::Bf1() [3] 0x0 [1] B1::ib1 = 11 [2] B1::cb1 = 1 [3] B::_vptr-> [0] B1::f() [1] B::Bf() [2] 0x1 [4] B::ib = 0 [5] B::cb = B [6] NULL = 0

最明显的差别就是这个单继承中多了一个虚函数表,单继承的情况下虚继承会增加一个基类自己的虚函数表,而非虚继承却没有.

而两个虚函数表中的,第一虚函数都是子类的虚函数f(),好像是正常的(运行时多态嘛)。但是,在上面的代码中,测试了一下pVtab[0][0]pVtab[3][0]输出的结果并不一样,也就是存在两个子类的f()函数,这特么刚摆好的桌子有木有啊(╯°Д°)╯.

那是不是哪里错了呢,如果取消代码中注释(添加三行代码),确实输出的是同样的东西但是函数地址不一样。并且,我不知道的原因这样程序运行不下去了。

gcc不好用看看微软怎么是什么情况吧。

ps:本来不想验证非虚继承函数是不是一个函数得,不是的话(╯°Д°)╯。幸好在gcc 6.1.1是它们地址是相同的┬─┬ ノ('-'ノ)

visual studio 2013

上来就多出来两个概念:虚函数表指针(_vftptr)、虚基类表指针(_bvtptr)。

但是微软编译器可以直接输出对象完整的内存布局啊,真实不错,有一统江湖的气质!方法:项目属性->配置属性->C/C++->命令行->其他选项添加/d1reportSingleClassLayout[类名]1。比如这里就是/d1reportSingleClassLayoutB1,也可以使用命令:cl classLayout.cpp /d1reportSingleClassLayoutB1,会得到下面的输出结果:

1>------ 已启动生成: 项目: Vptr, 配置: Debug Win32 ------ 1> main.cpp 1> class B1 size(32): 1> +--- 1> 0 | {vfptr} 1> 4 | {vbptr} 1> 8 | ib1 1> 12 | cb1 1> | <alignment member> (size=3) 1> +--- 1> 16 | (vtordisp for vbase B) 1> +--- (virtual base B) 1> 20 | {vfptr} 1> 24 | ib 1> 28 | cb 1> | <alignment member> (size=3) 1> +--- 1> 1> B1::$vftable@B1@: 1> | &B1_meta 1> | 0 1> 0 | &B1::f1 1> 1 | &B1::Bf1 1> 1> B1::$vbtable@: 1> 0 | -4 1> 1 | 16 (B1d(B1+4)B) 1> 1> B1::$vftable@B@: 1> | -20 1> 0 | &(vtordisp) B1::f 1> 1 | &B::Bf 1> 1> B1::f this adjustor: 20 1> B1::f1 this adjustor: 0 1> B1::Bf1 this adjustor: 0 1> 1> vbi: class offset o.vbptr o.vbte fVtorDisp 1> B 20 4 4 1 1> 1> 1> Vptr.vcxproj -> D:\Code_Inc\VS2012\Test\Vptr\Vptr\Debug\Vptr.exe

我们看到B1 vftable中却没有它的虚函数f()。所以编译器都好任性~~ 不过微软这个布局输出真心赞,不用再自己猜想了T_T.

虽然不完美,但是,我们看到虚继承类似于将虚基类的成员(虚函数和变量)放在一起,子类的放在了一起

有些话需要多次引用,

Lippman说,如果一个虚基类派生自另一虚基类,而且它们都支持虚函数和非静态数据成员的时候,编译器对虚基类的支持就像迷宫一样复杂。

强烈安利

《深度探索C++对象模型》,不过我一点没看呢,不过好像非常值得看。ps:评论里有一句,看了之后也不用跟着写几行代码,顶多一把一把的薅头发,反正不费电!

References:

  1. c++对象内存模型【内存布局】

results matching ""

    No results matching ""