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

时间: 2024-11-29 04:26:22

C++ 虚函数机制学习的相关文章

匹夫细说C#:从园友留言到动手实现C#虚函数机制

前言 上一篇文章匹夫通过CIL代码简析了一下C#函数调用的话题.虽然点击进来的童鞋并不如匹夫预料的那么多,但也还是有一些挺有质量的来自园友的回复.这不,就有一个园友提出了这样一个代码,这段代码如果被编译成CIL代码的话,对虚函数的调用会使用call而非callvirt: override string ToString() { return Base.ToString(); } 至于为何是这样,匹夫在回复中也做了解释,因为上面那段代码其实相当于是这样的: override string ToSt

[C/C++] 虚函数机制

转自:c++ 虚函数的实现机制:笔记 1.c++实现多态的方法 其实很多人都知道,虚函数在c++中的实现机制就是用虚表和虚指针,但是具体是怎样的呢?从more effecive c++其中一篇文章里面可以知道:是每个类用了一个虚表,每个类的对象用了一个虚指针.具体的用法如下: class A { public: virtual void f(); virtual void g(); private: int a }; class B : public A { public: void g();

从园友留言到动手实现C#虚函数机制

上一篇文章匹夫通过CIL代码简析了一下C#函数调用的话题.虽然点击进来的童鞋并不如匹夫预料的那么多,但也还是有一些挺有质量的来自园友的回复.这不,就有一个园友提出了这样一个代码,这段代码如果被编译成CIL代码的话,对虚函数的调用会使用call而非callvirt: override string ToString() { return Base.ToString(); } 至于为何是这样,匹夫在回复中也做了解释,因为上面那段代码其实相当于是这样的: http://paradise.ezla.co

纯虚函数的学习和使用

老师的要求是给出shape类,派生出梯形,圆形,正方形和矩形4个类,然后根据他们的面积进行排序并且输出相应的信息. 一开始我遇到了一个问题,我没有将4个派生类定义成数组,导致定义出来的某个类对象使用完之后就被抹除了,从而导致原先的基类指针数组的指向丢失.纯虚函数的工作原理就是指针指向这个类,所以这个类必须用数组保存起来,一旦被覆盖,指针的指向就会出现丢失或者紊乱的情况,但是经过我的观察,他好像指向最后 一次定义的对象,而是好像都是,但是具体里面是怎样实现的有待考究 .当时我还是一位是构造函数的问

C++ 多态、虚函数机制以及虚函数表

1.非virtual函数,调用规则取决于对象的显式类型.例如 A* a  = new B(); a->display(); 调用的就是A类中定义的display().和对象本体是B无关系. 2.virtual函数,具体调用哪个版本,取决于虚函数表.例如 A* a = new B(); a->v_display(); 这个时候,对象a就需要查找自身的虚函数表,表中的v_display()是一个函数指针,可能指向不同类中的对应的v_display函数并调用对应版本的v_display.一般而言,如

你好,C++(37)上车的人请买票!6.3.3 用虚函数实现多态

6.3.3  用虚函数实现多态 在理解了面向对象的继承机制之后,我们知道了在大多数情况下派生类是基类的“一种”,就像“学生”是“人”类中的一种一样.既然“学生”是“人”的一种,那么在使用“人”这个概念的时候,这个“人”可以指的是“学生”,而“学生”也可以应用在“人”的场合.比如可以问“教室里有多少人”,实际上问的是“教室里有多少学生”.这种用基类指代派生类的关系反映到C++中,就是基类指针可以指向派生类的对象,而派生类的对象也可以当成基类对象使用.这样的解释对大家来说是不是很抽象呢?没关系,可以

虚函数列表: 取出方法 // 虚函数工作原理和(虚)继承类的内存占用大小计算 32位机器上 sizeof(void *) // 4byte

#include <iostream> using namespace std; class A { public: A(){} virtual void geta(){ cout << "A:A" <<endl; } virtual void getb(){ cout << "A:B" <<endl; } }; class B :public A{ public: B(){} virtual void g

为什么析构函数是虚函数比较好?

原因:基类对象的指针操作派生类对象时,防止析构函数只调用基类的,而不调用派生类的 下面详细说明: //基类 class A{ public : A(){ cout<<"A构造函数"<<endl; } ~A(){cout<<"A被销毁了"<<endl;} void Do(){ cout<<"A要做点什么"<<endl; } }; //派生类 class B :public A{

《C++编程思想》 第十四章 多态和虚函数 (原书代码+习题+讲解)

一.相关知识点 函数调用捆绑 把函数体与函数调用相联系称为捆绑(binding).当捆绑在程序运行之前(由编译器和连接器)完成时,称为早捆绑.我们可能没有听到过这个术语,因为在过程语言中是不会有的:C编译只有一种函数调用,就是早捆绑.上面程序中的问题是早捆绑引起的,因为编译器在只有 instrument地址时它不知道正确的调用函数.解决方法被称为晚捆绑,这意味着捆绑在运行时发生,基于对象的类型.晚捆绑又称为动态捆绑或运行时捆绑.当一个语言实现晚捆绑时,必须有一种机制在运行时确定对象的类型和合适的