跟我一起学习C++虚函数--第五篇

       在前一篇,我们讨论了在多重继承情况下,具有虚函数的类的内存布局情况。本篇将进一步探索在多重虚拟继承情况下的内存布局情况。
    
      在讨论多重虚拟继承前,我们先对《 浅析GCC下C++多重继承 & 虚拟继承的对象内存布局》中的例子进行扩充。先看看带有虚函数的单一虚拟继承情况下类的内存布局。
        
        先看例子:
#include <iostream>
using namespace std;

class Top
{
public:
    virtual void x(){cout << "top x" << endl;}
    virtual void print0(){cout << "top print" << endl;}
public:
    int a;
};

class Left:virtual public Top
{
public:
    virtual void x(){cout << "left x" << endl;}
    virtual void print1(){cout << "left print" << endl;}
public:
    int b;
};


 int main()
 {
    /*first part*/
    cout << sizeof(Top) << "\t" << sizeof(Left) << endl;//输出:8  16
    Left *left = new Left();
    cout << left << " " << &left->b << " " << &left->a << endl; 
    //输出:0x8517008 0x851700c 0x8517014

    /*second part*/
    typedef void (*Func)(void);
    Func pFunc;
    pFunc = (Func)*((int *)*(int *)(left));
    pFunc();//输出:left x
    pFunc = (Func)*((int *)*(int *)(left)+1);
    pFunc();//输出:left print
//  pFunc = (Func)*((int *)*(int *)(left)+2);
//  pFunc();//段错误

    /*third part*/
    pFunc = (Func)*((int *)*((int *)(left)+2));
    pFunc();//输出:left x
    pFunc = (Func)*((int *)*((int *)(left)+2)+1);
    pFunc();//输出:top print
//  pFunc = (Func)*((int *)*((int *)(left)+2)+2);
//  pFunc();//段错误

    delete left;
    return 0;
 }

        从输出情况我们可以得知以下信息: 在具有虚函数的单一虚拟继承时,子类中会有多个虚指针以及多个虚函数表。我们可以用如下图来表示对应的内存布局:
         跟我一起学习C++虚函数--第五篇

        虚拟继承时,编译器会在子类中多插入一个父类的虚指针。
        
         下面我们正式讨论多重继承时的情况,请看例子:
#include <iostream>
using namespace std;

class Top
{
public:
    virtual void x(){cout << "top x" << endl;}
    virtual void print0(){cout << "top print" << endl;}
public:
    int a;
};

class Left:virtual public Top
{
public:
    virtual void y(){cout << "left y" << endl;}
    virtual void print1(){cout << "left print" << endl;}
public:
    int b;
};

class Right:virtual public Top
{
public:
    virtual void z(){cout << "right z" << endl;}
    virtual void print2(){cout << "right print" << endl;}
public:
    int c;
};

class Bottom : public Left, public Right
{
public:
    virtual void y(){cout << "bottom y" << endl;}
    virtual void z(){cout << "bottom z" << endl;}
    virtual void print3(){cout << "bottom print" << endl;}
public:
    int d;
};

 int main()
 {
    /*first part*/
    cout << sizeof(Top) << "\t" << sizeof(Left) << "\t" << sizeof(Right) << "\t" << sizeof(Bottom) << endl;
    //输出:8       16      16      28
    Bottom *b = new Bottom();
    cout << b << " " << &b->b << " " << &b->c << " " << &b->d << " " << &b->a << endl;
    //输出:0x9814008 0x981400c 0x9814014 0x9814018 0x9814020
	
    /*second part*/
    typedef void (*Func)(void);
    Func pFunc;
    pFunc = (Func)*((int *)*(int *)(b));
    pFunc();//输出:bottom y 
    pFunc = (Func)*((int *)*(int *)(b)+1);
    pFunc();//输出:left print
    pFunc = (Func)*((int *)*(int *)(b)+2);
    pFunc();//输出:bottom z
    pFunc = (Func)*((int *)*(int *)(b)+3);
    pFunc();//输出:bottom print
//  pFunc = (Func)*((int *)*(int *)(b)+4);
//  pFunc();//段错误

    /*third part*/
    pFunc = (Func)*((int *)*((int *)(b)+2));
    pFunc();//输出:bottom z
    pFunc = (Func)*((int *)*((int *)(b)+2)+1);
    pFunc();//输出:right print
//  pFunc = (Func)*((int *)*((int *)(b)+2)+2);
//  pFunc();//段错误

    /*fourth part*/
    pFunc = (Func)*((int *)*((int *)(b)+5));
    pFunc();//输出:top x
    pFunc = (Func)*((int *)*((int *)(b)+5)+1);
    pFunc();//输出:top print
//  pFunc = (Func)*((int *)*((int *)(b)+5)+2);
//  pFunc();//段错误

    delete b;
    return 0;
 }


 对于上面的例子,我们分为四部分进行讲解。

第一部分:多重虚拟继承情况下,对象本身(除虚函数表外)的内存布局。

        从代码中first part的输出情况来看,结合本文最开始对单一虚拟继承的讨论,Top、Left和Right的大小很容易理解,至于Bottom类的大小,如果你看过参考文献一,那么也很容易理解。在虚拟继承的情况下,Bottom类只包含了一次Top类中的a成员变量,因此总共有4个int成员变量,为16字节。再加上三个虚指针,即为28字节。

       从输出的Bottom对象中成员变量地址情况,我们可以用如下图来表示内存布局:
         跟我一起学习C++虚函数--第五篇
        
        简单地说,多重虚拟继承时,子类会有一个祖父类的存在,包括祖父类的成员变量和虚函数。

第二部分:多重虚拟继承情况下,主要虚函数表的内存布局。

        从代码的second part的输出情况来看,我们可以用下图来表示主要虚函数表的内存布局:
       跟我一起学习C++虚函数--第五篇
       
第三部分:多重虚拟继承情况下,次要虚函数表的内存布局。

        从代码的third part的输出情况来看,我们可以用下图来表示次要虚函数表的内存布局:
         跟我一起学习C++虚函数--第五篇

第四部分:多重虚拟继承情况下,次要祖父虚函数表的内存布局。

        从代码的fourth part的输出情况来看,我们可以用下图来表示祖父虚函数表的内存布局:
         跟我一起学习C++虚函数--第五篇
        
         对比前一篇多重继承情况下类的内存布局,我们可以发现: 在多重虚拟继承情况下,祖父类只会在子类中具有一份存在,包括祖父类的成员变量和虚函数。子类中具有一个主要虚函数表和多个次要虚函数。主要虚函数表包括父类1中被覆盖的虚函数、父类1中没有被覆盖的虚函数、其他父类中被覆盖的虚函数以及子类独有的虚函数。次要虚函数表中包括被子类覆盖的虚函数以及未被子类覆盖的虚函数。

 
         本系列(完)。
        
参考文献:

          1. 《浅析GCC下C++多重继承 & 虚拟继承的对象内存布局》

          2. 《深度探索C++对象模型》

你可能感兴趣的:(C++,虚函数,内存布局,多重虚拟继承)