【C++编程】C++虚函数表剖析 ③

钻石型多重虚继承

钻石型-重复继承的源代码如下所示。其中,每个类都有两个变量,一个是整形(4字节),一个是字符(1字节)

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; }
};

class B2 : virtual public B
{
public:
    int ib2;
    char cb2;
public:
    B2() :ib2(12), cb2('2') {}
    virtual void f() { cout << "B2::f()" << endl; }
    virtual void f2() { cout << "B2::f2()" << endl; }
    virtual void Bf2() { cout << "B2::Bf2()" << endl; }
};

class D : public B1, public B2
{
public:
    int id;
    char cd;
public:
    D() :id(100), cd('D') {}
    virtual void f() { cout << "D::f()" << endl; }
    virtual void f1() { cout << "D::f1()" << endl; }
    virtual void f2() { cout << "D::f2()" << endl; }
    virtual void Df() { cout << "D::Df()" << endl; }
};

图解:

钻石型虚继承内存布局:

 

 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 

 

参考资料

posted @ 2018-08-12 21:14  苏格拉底的落泪  阅读(517)  评论(0编辑  收藏  举报