C++ 虚函数机制学习
致谢
本文是基于对<Inside the c++ object model>的阅读和gdb的使用而完成的.在此感谢Lippman对cfront中对象模型的解析,这些解析帮助读者拨开迷雾.此外,Linux下无比强大的gdb工具更是驱散"黑暗"的"明灯". :)
No-Inheritance
1 class Base { 2 public: 3 int a = 21; 4 static int b; 5 int c = 22; 6 7 void showBase1(); 8 static int showBase2(); 9 }; 10 11 void Base::showBase1() { 12 cout<<"Base"<<endl; 13 } 14 int Base::showBase2() { 15 cout<<"base2"<<endl; 16 }
解析:
使用GDB查看内存空间,显示
Base中a的地址为0x7fffffffdc50
Base中c的地址为0x7fffffffdc54
Base中b的地址为0x601068
showBase1的地址为0x40085e(参数为Base * const -> this指针为常量指针)
showBase2的地址为0x400888(参数为void)
显然
non-static data member是存储在class object中;
static data member, member function因为是所有本class的对象所share的,所以放置在了一个公共区域;
Inheritance without Polymorphism
1 class Base { 2 public: 3 int a = 21; 4 static int b; 5 int c = 22; 6 7 void showBase1(); 8 }; 9 int Base::b = 10; 10 void Base::showBase1() { 11 cout<<"Base"<<endl; 12 } 13 14 class Inheri : public Base { 15 public: 16 int c = 23; 17 static int d; 18 19 static int showInheri1(); 20 }; 21 int Inheri::d = 11; 22 int Inheri::showInheri1(){ 23 cout<<"Inheri"<<endl; 24 }
解析:
使用GDB查看内存空间,显示
Inheri中a的地址为0x7fffffffdc70
Inheri中c的地址为0x7fffffffdc78
Inheri中b的地址为0x601068
Inheri中b的地址为0x60106c
showBase1的地址为0x40085e(参数为Base * const -> this指针为常量指针)
showInheri1的地址为0x400888(参数为void)
显然
derived class object中包含 基类和派生类的non-static data member;
No-Inheritance with Polymorphism
1 class Base { 2 public: 3 int a = 21; 4 static int b; 5 int c = 22; 6 7 virtual void showBase1();//virtual function 8 }; 9 int Base::b = 10; 10 void Base::showBase1() { 11 cout<<"Base"<<endl; 12 }
使用gdb查看base class object( p ptr )
" {_vptr.Base = 0x400af0 <vtable for Base+16>, a = 21, static b = 10, c = 22} "
在类中使用虚机制(虚函数,虚基类,虚继承..)时,会为每个object添加vptr来指向所对应的vtbl.
使用gdb查看vptr指向的虚函数(p /a *(void**)0x400af0
" {0x40092e <Base::showBase1()>, 0x697265686e4936} "
Single-Inheritance with Virtual Mechanism
class Base { public: int a = 21; static int b; int c = 22; virtual void showBase1(); }; int Base::b = 10; void Base::showBase1() { cout<<"Base"<<endl; } class Inheri : public Base { public: int c = 23; static int d; static int showInheri1(); }; int Inheri::d = 11; int Inheri::showInheri1(){ cout<<"Inheri"<<endl; }
查看Inheri class object
{<Base> = {_vptr.Base = 0x400bc0 <vtable for Inheri+16>, a = 21, static b = 10, c = 22}, c = 23, static d = 11}
查看Ineri class object 的vtbl
0x400998 <Inheri::show()>
可以看到derived class object直接使用了从base class subobject中继承而来的vptr.
同样,查看Base class object
{_vptr.Base = 0x400be0 <vtable for Base+16>, a = 21, static b = 10, c = 22}
查看Base class object的vtbl
0x40096e <Base::show()>
从这里可以看出来,在Single Inheritance中每个class object的vtbl中都只包含本class所对应的virtual function.
我们再测试一下derived class赋值给base class pointer的情况.
Base* bbptr = new Inheri;
查看bbptr所指向的内存:
{<Base> = {_vptr.Base = 0x400bc0 <vtable for Inheri+16>, a = 21, static b = 10, c = 22}, c = 23, static d = 11}
查看vtbl中的内容:
0x400998 <Inheri::show()>
这里我们可以有两点发现:
1) 虽然使用的是基类指针Base* 来接Inheri对象,但是其vptr所指向的vtbl仍然是Inheri class的;
2) 每个class所对应的vtbl在内存中只有一份,在测试中bbptr和iptr指向的vtbl都是位于0x400bc0
Multiple-Inheritance with Virtual Mechanism
1 class Base { 2 public: 3 int a = 21; 4 static int b; 5 int c = 22; 6 7 virtual void show();//inline 8 }; 9 int Base::b = 10; 10 void Base::show() { 11 cout<<"Base"<<endl; 12 } 13 14 class Inheri : public Base { 15 public: 16 int c = 23; 17 static int d; 18 19 virtual void show(); 20 }; 21 int Inheri::d = 11; 22 void Inheri::show(){ 23 cout<<"Inheri"<<endl; 24 } 25 26 class OtherBase{ 27 public: 28 int oa; 29 virtual void show(); 30 }; 31 void OtherBase::show(){ 32 cout<<"OtherBase"<<endl; 33 } 34 35 class Final : public OtherBase,public Inheri{ 36 public: 37 virtual void show(); 38 }; 39 void Final::show() { 40 cout<<"Final"<<endl; 41 }
查看Final对象
"{<OtherBase> = {_vptr.OtherBase = 0x400cd0 <vtable for Final+16>, oa = 0}, <Inheri> = {<Base> = {_vptr.Base = 0x400ce8 <vtable for Final+40>, a = 21, static b = 10, c = 22}, c = 23, static d = 11}, <No data fields>}"
可以观察到:
1) 子对象从右向左的被构建
2) 分别包含OtherBase和Base的vptr,这是为了在derived class object 赋予base class object时更容易处理.
继续查看OtherBase和Base中vptr的信息
_vptr.OtherBase所指向的vtbl中信息为 : 0x400a5c <Final::show()>
_vptr.Base所指项的vtbl中信息为 : 0x400a86 <_ZThn16_N5Final4showEv>
可以看到这两个vtbl所保存的都是Final::show.因此, 通过Final来为各个基类指针赋值时,最后总是调用Final自身的虚函数.
Virtual Inheritance
1 class _ios { 2 public: 3 int i; 4 virtual void show(); 5 }; 6 void _ios::show() { 7 cout<<"ios"<<endl; 8 } 9 10 11 class _istream : public _ios { 12 public: 13 int is; 14 virtual void show(); 15 }; 16 void _istream::show() { 17 cout<<"istream"<<endl; 18 } 19 20 class _ostream : public _ios { 21 public: 22 int os; 23 virtual void show(); 24 }; 25 void _ostream::show() { 26 cout<<"ostream"<<endl; 27 } 28 29 class _iostream : public _istream, public _ostream { 30 public: 31 int ios; 32 virtual void show(); 33 }; 34 void _iostream::show() { 35 cout<<"iostream"<<endl; 36 }
在未使用virtual inheritance时, 查看 _iostream 对象, 会看到两份 _ios 类的对象,分别属于_istream和_ostream :
"{<_istream> = {<_ios> = {_vptr._ios = 0x400c90 <vtable for _iostream+16>, i = 0}, is = 0}, <_ostream> = {<_ios> = {
_vptr._ios = 0x400ca8 <vtable for _iostream+40>, i = 0}, os = 0}, ios = 0}"
符合之前介绍的single inheritance with polymorphism, _istream和_ostream分别使用从_ios中而来的vptr.ios来指向自己的vtbl.
使用virtual inheritance时 ,可以看到只有一份 _ios对象:
"{<_istream> = {<_ios> = {_vptr._ios = 0x400d18 <vtable for _iostream+88>, i = 0}, _vptr._istream = 0x400cd8 <vtable for _iostream+24>, is = 0}, <_ostream> = {_vptr._ostream = 0x400cf8 <vtable for _iostream+56>, os = 0}, ios = 0}"
在虚继承中,没有和单一继承中那样继承基类的vptr, 而是拥有自己的vptr.
继续查看vtbl中内容,分别显示 :
0x400a40 <_ZTv0_n24_N9_iostream4showEv>
0x400a3a <_ZThn16_N9_iostream4showEv>
可见, 虚函数表中的函数也都是_iostream class中的member function.因此, 无论_iostream对象赋值给那个base class subobject的指针,总能调用到_iostream class的virtual function.
Reference
<Inside the C++ Object Model>
<GDB Manul>
备注
更多内容详见 https://github.com/CarlSama/Inside-The-CPP-Object-Model-Reading-Notes
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。