zoukankan      html  css  js  c++  java
  • 【C++ Primer | 15】C++虚函数表剖析②

    多重继承

    下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。

    注意:子类只overwrite了父类的f()函数,而还有一个是自己的函数(我们这样做的目的是为了用g1()作为一个标记来标明子类的虚函数表)。而且每个类中都有一个自己的成员变量:

    们的类继承的源代码如下所示:父类的成员初始为10,20,30,子类的为100

      1 #include<iostream>
      2 using namespace std;
      3 
      4 class Base1 {
      5 public:
      6     int ibase1;
      7     Base1() :ibase1(10) {}
      8     virtual void f() { cout << "Base1::f()" << endl; }
      9     virtual void g() { cout << "Base1::g()" << endl; }
     10     virtual void h() { cout << "Base1::h()" << endl; }
     11 
     12 };
     13 
     14 class Base2 {
     15 public:
     16     int ibase2;
     17     Base2() :ibase2(20) {}
     18     virtual void f() { cout << "Base2::f()" << endl; }
     19     virtual void g() { cout << "Base2::g()" << endl; }
     20     virtual void h() { cout << "Base2::h()" << endl; }
     21 };
     22 
     23 class Base3 {
     24 public:
     25     int ibase3;
     26     Base3() :ibase3(30) {}
     27     virtual void f() { cout << "Base3::f()" << endl; }
     28     virtual void g() { cout << "Base3::g()" << endl; }
     29     virtual void h() { cout << "Base3::h()" << endl; }
     30 };
     31 
     32 class Derive : public Base1, public Base2, public Base3 {
     33 public:
     34     int iderive;
     35     Derive() :iderive(100) {}
     36     virtual void f() { cout << "Derive::f()" << endl; }
     37     virtual void g1() { cout << "Derive::g1()" << endl; }
     38 };
     39 
     40 int main()
     41 {
     42     typedef void(*Fun)(void);
     43 
     44     Derive d;
     45 
     46     int** pVtab = (int**)&d;
     47 
     48     cout << "[0] Base1::_vptr->" << endl;
     49     Fun pFun = (Fun)pVtab[0][0];
     50     cout << "     [0] ";
     51     pFun();
     52 
     53     pFun = (Fun)pVtab[0][1];
     54     cout << "     [1] "; pFun();
     55 
     56     pFun = (Fun)pVtab[0][2];
     57     cout << "     [2] "; pFun();
     58 
     59     pFun = (Fun)pVtab[0][3];
     60     cout << "     [3] "; pFun();
     61 
     62     pFun = (Fun)pVtab[0][4];
     63     cout << "     [4] "; cout << pFun << endl;
     64 
     65     cout << "[1] Base1.ibase1 = " << (int)pVtab[1] << endl;
     66 
     67     int s = sizeof(Base1) / 4;
     68 
     69     cout << "[" << s << "] Base2::_vptr->" << endl;
     70     pFun = (Fun)pVtab[s][0];
     71     cout << "     [0] "; pFun();
     72 
     73     pFun = (Fun)pVtab[s][1];
     74     cout << "     [1] "; pFun();
     75 
     76     pFun = (Fun)pVtab[s][2];
     77     cout << "     [2] "; pFun();
     78 
     79     pFun = (Fun)pVtab[s][3];
     80     cout << "     [3] ";
     81     cout << pFun << endl;
     82 
     83     cout << "[" << s + 1 << "] Base2.ibase2 = " << (int)pVtab[s + 1] << endl;
     84 
     85     s = s + sizeof(Base2) / 4;
     86 
     87     cout << "[" << s << "] Base3::_vptr->" << endl;
     88     pFun = (Fun)pVtab[s][0];
     89     cout << "     [0] "; pFun();
     90 
     91     pFun = (Fun)pVtab[s][1];
     92     cout << "     [1] "; pFun();
     93 
     94     pFun = (Fun)pVtab[s][2];
     95     cout << "     [2] "; pFun();
     96 
     97     pFun = (Fun)pVtab[s][3];
     98     cout << "     [3] ";
     99     cout << pFun << endl;
    100 
    101     s++;
    102     cout << "[" << s << "] Base3.ibase3 = " << (int)pVtab[s] << endl;
    103     s++;
    104     cout << "[" << s << "] Derive.iderive = " << (int)pVtab[s] << endl;
    105 }

     输出结果:

    使用图片表示是下面这个样子:

    我们可以看到:

    • 每个父类都有自己的虚表。
    • 子类的成员函数被放到了第一个父类的表中。
    • 内存布局中,其父类布局依次按声明顺序排列。
    • 每个父类的虚表中的f()函数都被overwrite成了子类的f()。这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

    重复继承

    面我们再来看看,发生重复继承的情况。所谓重复继承,也就是某个基类被间接地重复继承了多次。

    下图是一个继承图,我们重载了父类的f()函数。

    其类继承的源代码如下所示。其中,每个类都有两个变量,一个是整形(4字节),一个是字符(1字节),而且还有自己的虚函数,自己overwrite父类的虚函数。如子类D中,f()覆盖了超类的函数, f1() 和f2() 覆盖了其父类的虚函数,Df()为自己的虚函数。

      1 #include<iostream>
      2 using namespace std;
      3 
      4 class B
      5 {
      6 public:
      7     int ib;
      8     char cb;
      9 public:
     10     B() :ib(0), cb('B') {}
     11 
     12     virtual void f() { cout << "B::f()" << endl; }
     13     virtual void Bf() { cout << "B::Bf()" << endl; }
     14 };
     15 class B1 : public B
     16 {
     17 public:
     18     int ib1;
     19     char cb1;
     20 public:
     21     B1() :ib1(11), cb1('1') {}
     22 
     23     virtual void f() { cout << "B1::f()" << endl; }
     24     virtual void f1() { cout << "B1::f1()" << endl; }
     25     virtual void Bf1() { cout << "B1::Bf1()" << endl; }
     26 
     27 };
     28 class B2 : public B
     29 {
     30 public:
     31     int ib2;
     32     char cb2;
     33 public:
     34     B2() :ib2(12), cb2('2') {}
     35 
     36     virtual void f() { cout << "B2::f()" << endl; }
     37     virtual void f2() { cout << "B2::f2()" << endl; }
     38     virtual void Bf2() { cout << "B2::Bf2()" << endl; }
     39 
     40 };
     41 
     42 class D : public B1, public B2
     43 {
     44 public:
     45     int id;
     46     char cd;
     47 public:
     48     D() :id(100), cd('D') {}
     49 
     50     virtual void f() { cout << "D::f()" << endl; }
     51     virtual void f1() { cout << "D::f1()" << endl; }
     52     virtual void f2() { cout << "D::f2()" << endl; }
     53     virtual void Df() { cout << "D::Df()" << endl; }
     54 
     55 };
     56 
     57 int main()
     58 {
     59     typedef void(*Fun)(void);
     60     int** pVtab = NULL;
     61     Fun pFun = NULL;
     62 
     63     D d;
     64     pVtab = (int**)&d;
     65     cout << "[0] D::B1::_vptr->" << endl;
     66     pFun = (Fun)pVtab[0][0];
     67     cout << "     [0] ";    pFun();
     68     pFun = (Fun)pVtab[0][1];
     69     cout << "     [1] ";    pFun();
     70     pFun = (Fun)pVtab[0][2];
     71     cout << "     [2] ";    pFun();
     72     pFun = (Fun)pVtab[0][3];
     73     cout << "     [3] ";    pFun();
     74     pFun = (Fun)pVtab[0][4];
     75     cout << "     [4] ";    pFun();
     76     pFun = (Fun)pVtab[0][5];
     77     cout << "     [5] 0x" << pFun << endl;
     78 
     79     cout << "[1] B::ib = " << (int)pVtab[1] << endl;
     80     cout << "[2] B::cb = " << (char)pVtab[2] << endl;
     81     cout << "[3] B1::ib1 = " << (int)pVtab[3] << endl;
     82     cout << "[4] B1::cb1 = " << (char)pVtab[4] << endl;
     83 
     84     cout << "[5] D::B2::_vptr->" << endl;
     85     pFun = (Fun)pVtab[5][0];
     86     cout << "     [0] ";    pFun();
     87     pFun = (Fun)pVtab[5][1];
     88     cout << "     [1] ";    pFun();
     89     pFun = (Fun)pVtab[5][2];
     90     cout << "     [2] ";    pFun();
     91     pFun = (Fun)pVtab[5][3];
     92     cout << "     [3] ";    pFun();
     93     pFun = (Fun)pVtab[5][4];
     94     cout << "     [4] 0x" << pFun << endl;
     95 
     96     cout << "[6] B::ib = " << (int)pVtab[6] << endl;
     97     cout << "[7] B::cb = " << (char)pVtab[7] << endl;
     98     cout << "[8] B2::ib2 = " << (int)pVtab[8] << endl;
     99     cout << "[9] B2::cb2 = " << (char)pVtab[9] << endl;
    100 
    101     cout << "[10] D::id = " << (int)pVtab[10] << endl;
    102     cout << "[11] D::cd = " << (char)pVtab[11] << endl;
    103 }

    输出结果:

    下面是对于子类实例中的虚函数表的图:(第一份图为原作者的图,第二幅图为修改的图)

    我们可以看见,最顶端的父类B其成员变量存在于B1和B2中,并被D给继承下去了。而在D中,其有B1和B2的实例,于是B的成员在D的实例中存在两份,一份是B1继承而来的,另一份是B2继承而来的。所以,如果我们使用以下语句,则会产生二义性编译错误:

     D d;
    d.ib = 0; //二义性错误
    d.B1::ib = 1; //正确
    d.B2::ib = 2; //正确

    注意,上面例程中的最后两条语句存取的是两个变量。虽然我们消除了二义性的编译错误,但B类在D中还是有两个实例,这种继承造成了数据的重复,我们叫这种继承为重复继承。重复的基类数据成员可能并不是我们想要的。所以,C++引入了虚基类的概念。

    钻石型多重虚继承

    1. 虚继承

     1 class B
     2 {
     3 public:
     4     int _b;
     5 };
     6 class C1 :virtual public B
     7 {
     8 public:
     9     int _c1;
    10 };
    11 class C2 :virtual public B
    12 {
    13 public:
    14     int _c2;
    15 };
    16 class D :public C1, public C2
    17 {
    18 public:
    19     int _d;
    20 };
    21 
    22 int main()
    23 {
    24     D d;
    25     d._d = 4;
    26     return 0;
    27 }

    内存布局:先是C1类中的成员,再是C2类中的成员,最后是D类自己的成员,如下图:

     2. 虚函数

    虚拟继承的出现就是为了解决重复继承中多个间接父类的问题的。钻石型的结构是其最经典的结构。也是我们在这里要讨论的结构:

    上述的“重复继承”只需要把B1和B2继承B的语法中加上virtual 关键,就成了虚拟继承,其继承图如下所示:

    上图和前面的“重复继承”中的类的内部数据和接口都是完全一样的,只是我们采用了虚拟继承,其省略后的源码如下所示:

    1 class B {……};
    2 class B1 : virtual public B{……};
    3 class B2: virtual public B{……};
    4 class D : public B1, public B2{ …… };

     

    在看菱形虚拟继承之前,我们先看一下简单的虚拟单继承是怎么样的,这样便于我们理解复杂一点的菱形虚拟继承,我们先看一组代码:

     1 class A {
     2 public:
     3     int _a;
     4     virtual void fun1() {}
     5 };
     6 
     7 class B : public virtual A {
     8 public:
     9     int _b;
    10     //virtual void fun1() {}
    11     //virtual void fun2() {}
    12 };
    13 
    14 
    15 int main()
    16 {
    17     B b;
    18     b._a = 2;
    19     b._b = 1;
    20     cout << sizeof(A) << endl;
    21     cout << sizeof(B) << endl;
    22     getchar();
    23     return 0;
    24 }

    在VS2013的测试结果为8和16,我们试着去掉 //virtual void fun1() {}的注释,也就是

    1 class B : public virtual A {
    2 public:
    3     int _b;
    4     virtual void fun1() {}
    5     //virtual void fun2() {}
    6 };

    此时测试结果仍为8和16,但是当我们去掉//virtual void fun2() {}的注释,也就是

    1 class B : public virtual A {
    2 public:
    3     int _b;
    4     virtual void fun1() {}
    5     virtual void fun2() {}
    6 };

    测试结果为sizeof(A) = 8,sizeof(B) = 20。这是为什么?为了解决这个问题,我们有必要看看在这几种情况下的B对象模型,A类对象模型比较简单,我们知道虚函数必有一个指向虚表的指针,再加上A类对象本身有个int型数据加起来就是8。而对于B对象模型,我们可以简单分几种情况: 
    子类有覆盖(重写)且没有新增虚函数 and 子类没有覆盖(重写)且没有新增虚函数:这两种情况并没有太大差别,对于B对象模型都是下面这种:

    唯一的区别就是基类A的虚表指针指向的虚表有没有被重写而已,因此在第一种和第二种情况下,sizeof(B) = 16。

    而对于有新增虚函数这种情况,对于B的对象模型则是这样的:

    因为有重写基类的虚函数了,所以子类需要额外加一个虚表指针,这样sizeof(B) =20就不难理解了。有了这些知识,我们再看菱形虚拟继承就容易多了,首先对于菱形虚拟继承,它的继承层次图大概像下面这个样子:

     

    为了便于分析,我们可以把这个图拆解下来,也就是说从B到B1,B2是两个单一的虚拟继承,而从B1,B2到则是多继承,这样一来,问题就变得简单多了。对于B到B1,B2两个单一的虚拟继承,根据前面讲的很容易得到B1,B2的对象模型:

     接下来就是多继承,这样终于得到了我们D d的对象模型了:

     

     

    3. 最后再看几道有关的虚继承的题目

     

    对这四种情况分别求sizeof(a), sizeof(b)。结果是什么样的呢?我在VS2013的win32平台测试结果为: 
    第一种:4,12 
    第二种:4,4 
    第三种:8,16 
    第四种:8,8 

    参考资料

  • 相关阅读:
    用友U8 | 【出纳管理】添加日记账时,为什么日期选不了之前的日期?
    用友U8 | 【总账】结账时提示:该凭证已被别的用户锁定,请稍候在试...
    用友U8 | 【实施导航】实施导航进度条一直显示没完成
    利用Action方法委托重构switch接口
    关于wcf序列化后的压缩示例
    sql常用的命令
    WebBrowser通过cookie自动登录网站
    SqlServer大数据的分区方案
    WebBrowser 登录windows集成验证的网站
    SQL大批量插入数据的方式(多表关联) .
  • 原文地址:https://www.cnblogs.com/sunbines/p/9464931.html
Copyright © 2011-2022 走看看