• C++对象模型


      本文主要对C++对象模型做一个简单总结。主要讨论以下几种情况下的C++对象的内存布局情况。

    1)      单一的一般继承

    2)      单一的虚拟继承

    3)      多重继承

    4)      重复多重继承

    5)      钻石型的虚拟多重继承

    虚函数

    先简单介绍一下虚函数的机制。虚函数的主要作用是实现了多态的机制。对于多态,简而言之就是用父类型的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。从而让父类的指针有“多种形态”,这是一种泛型技术。

    都知道虚函数是通过一张虚表来实现的,在这个表中主要是一个类的虚函数的地址列表。在有虚函数的类的实例中这个表就被分配在这个实例的内存中,它就像一个地图,指向实际应该调用的函数。为了保证取到虚函数表的有最高的性能,C++编译器一般会保证虚函数表的指针存放于对象实例中最前面的位置。这意味着我们可以通过对象实例的地址来获得这张虚函数表,从而遍历其中所有的函数指针,并调用。

    下面给出一个实际的例子。

    1 class Base {
    2      public:
    3             virtual void f() { cout << "Base::f" << endl; }
    4             virtual void g() { cout << "Base::g" << endl; }
    5             virtual void h() { cout << "Base::h" << endl; }
    6  
    7 };

    我们可以通过Base的实例来得到虚函数表,使用如下代码:

     1 typedef void(*Fun)(void);
     2  
     3 Base b;
     4  
     5 Fun pFun = NULL;
     6  
     7 cout << "虚函数表指针的地址:" << (intptr_t*)(&b) << endl;
     8 cout << "虚函数表 — 第一个函数地址:" << (intptr_t*)*(intptr_t*)(&b) << endl;
     9  
    10 // Invoke the first virtual function
    11 pFun = (Fun)*((intptr_t*)*(intptr_t*)(&b));
    12 pFun();

    运行结果如下:

    我们强行把&b转成intptr_t *,取得虚函数表指针的地址,然后再次取址就得到第一个虚函数的地址了,即Base::f()。同样的,我们如果要调用Base::g()和Base::h(),可以使用如下代码:

    1 (Fun)*((intptr_t*)*(intptr_t*)(&b)+0);  // Base::f()
    2 (Fun)*((intptr_t*)*(intptr_t*)(&b)+1);  // Base::g()
    3 (Fun)*((intptr_t*)*(intptr_t*)(&b)+2);  // Base::h()

    下图可以帮助理解:

    注意:上面这个图中,虚函数表的最后有一个点,这个是虚函数表的结束点。这个值在不同的编译器下是不同的。在win8.1+vs2013中,这个值是NULL。在Ubuntu 14.04+GCC4.8.2中,如果这个值为1,表示还有下一个虚函数表(多重继承),如果值是0,则表示是最后一个虚函数表。

    单一的一般继承

    下面,我们假设有如下一种继承关系,父类、子类、子类的子类都有自己的一个成员变量。子类覆盖了父类的f()方法,子类的子类覆盖了子类的g_child()及f()。源代码如下:

     1 class Parent {
     2 public:
     3     int iparent;
     4     Parent ():iparent (10) {}
     5     virtual void f() { cout << " Parent::f()" << endl; }
     6     virtual void g() { cout << " Parent::g()" << endl; }
     7     virtual void h() { cout << " Parent::h()" << endl; }
     8  
     9 };
    10  
    11 class Child : public Parent {
    12 public:
    13     int ichild;
    14     Child():ichild(100) {}
    15     virtual void f() { cout << "Child::f()" << endl; }
    16     virtual void g_child() { cout << "Child::g_child()" << endl; }
    17     virtual void h_child() { cout << "Child::h_child()" << endl; }
    18 };
    19  
    20 class GrandChild : public Child{
    21 public:
    22     int igrandchild;
    23     GrandChild():igrandchild(1000) {}
    24     virtual void f() { cout << "GrandChild::f()" << endl; }
    25     virtual void g_child() { cout << "GrandChild::g_child()" << endl; }
    26     virtual void h_grandchild() { cout << "GrandChild::h_grandchild()" << endl; }
    27 };

    我们使用以下程序作为测试程序:

     1     typedef void(*Fun)(void);
     2     Fun pFun = NULL;
     3     GrandChild gc;
     4 
     5     intptr_t** pVtab = (intptr_t**)&gc;
     6 
     7     cout << "[0] GrandChild::_vptr->" << endl;
     8     for (int i = 0; (Fun)pVtab[0][i] != NULL; i++){
     9         pFun = (Fun)pVtab[0][i];
    10         cout << "    [" << i << "] ";
    11         pFun();
    12     }
    13     int *pData = (int *)((intptr_t *)&gc + 1);
    14 
    15     cout << "[1] Parent.iparent = " << (int)pData[0] << endl;
    16     cout << "[2] Child.ichild = " << (int)pData[1] << endl;
    17     cout << "[3] GrandChild.igrandchild = " << (int)pData[2] << endl;

    运行结果如下:

    使用图片表示如下:

    可以看出:

      1)虚函数表指针在对象内存的起始位置

      2)成员变量根据其继承和声明的顺序依次排在后面

      3)在单一继承中,被override的虚函数在虚函数表中得到了更新

    多重继承

    (未完待续)

  • 相关阅读:
    C#学习笔记——读写ini文件
    C#学习笔记——控件
    C#学习笔记——SerialPort类
    halcon学习笔记——遍历文件夹与文件选择
    halcon学习笔记——图像的预处理
    C#中创建和使用动态链接库
    C#学习笔记——控件的命名规范
    halcon学习笔记——(1)HDevelop language(语法结构特点)
    C#学习笔记——基础概念回顾
    C#学习笔记——继承窗体
  • 原文地址:https://www.cnblogs.com/codinglol/p/4757947.html
Copyright © 2020-2023  润新知