虚继承

说来惭愧,学习C++好几年了,但是从来没有接触过虚继承,今天总算有时间让我架起windbg一探究竟。
下面以狮子(Lion) 和老虎(Tiger)为例来说明,它们继承自动物(Animal),它们所生的后代是狮虎兽(Liger)。


lass Animal
{
public:
    Animal(){}
    int age;
    int weight;
};

class Lion : virtual public Animal
{
public:
    Lion(){}
    int speed;
};

class Tiger : virtual public Animal
{
public:
    Tiger(){}
    int power;
};

class Liger : public Lion, public Tiger
{
public:
    Liger(){}
    int color;
};

int main()
{
    Liger* liger = new Liger();
    liger->age      = 5;
    liger->color    = 0xFF22FF22;
    liger->power    = 100;
    liger->speed    = 200;
    liger->weight   = 300;
   
    Animal* animal = liger;
    animal->age    = animal->age + 1;
    animal->weight = animal->weight + 1;

    int p = sizeof(liger);

    Lion* lion = liger;
    p = lion->age;
    p = lion->speed;
    p = lion->weight;

    Tiger* tiger = liger;
    p = tiger->age;
    p = tiger->power;
    p = tiger->weight;

    return 0;
}


Liger* liger = new Liger();
    1) 编译器为liger在栈上分配一个大小为28的memory,并且调用Liger()构造函数。操作符new分配memory的功能是通过调用标准库中的malloc实现的。
    2)编译器将liger的首地址,做为构造函数Liger()的参数,这个地址就是我们属性的this指针。虽然在C++代码中,我们认为这个构造函数是没有参数的,但实际上编译器隐式的为此函数提供了this指针。
    3)刚进入Liger构造函数的时候,liger指向的memory是一块没有被加工过的空间(里面的数据是无效的),构造函数的功能就是对这个memory进行布局和赋值。
     
指针首地址 地址偏移 liger指向的memory 内容 赋值操作
Liger->
Lion->
+00h Liger::`vbtable' (008d6870) 00000000 00000014 00000000 Liger()构造函数中
  +04h speed 200 liger->speed = 200
Tiger-> +08h Liger::`vbtable' (008d687c) 00000000 0000000c 00000000 Liger()构造函数中
  +0Ch power 100 liger->power = 100
  +10h color 0xFFFF liger->color = 0xFFFF
Animal-> +14h age 5 liger->age = 5;
  +18h weight 300 liger->weight = 300

     4)首先调用的父类构造函数为Animal(),它的this指针为liger+14h。同样编译器也将此this指针传递给Animal(),从而对Animal指向的memory进行布局和赋值。
     5)调用Lion()构造函数,其首地址为liger+0,在Lion()构造函数中不会再次调用其父类Animal的构造函数(编译器决定)。
     6)调用Tiger()构造函数,其首地址为liger+08h,也不会调用Animal的构造函数。

liger->age  = 5;
     7)得到vbtable (008d6870) 中地址偏移为+4的值00000014放入edx寄存器中,即edx = 14。
     8)将5赋值给liger+edx的memory, 实现了对liger->age的赋值,即move dword ptr [ecx+14], 5。

liger->color = 0xFFFF;
     9)将0xFFFF赋值给liger + 10h指向的memory。
liger->power = 100;
     10)将100赋值给liger+0Ch指向的memory
liger->weight = 300;
     11)获得vbtable (008d6870) 的地址
     12)将虚表中偏移为+4的内容赋值给edx,即edx = 14h
     13)将300赋值给liger + edx + 4 指向的memory。编译器根据当前的首地址决定使用其指向虚表,Animal中的成员变量都需要通过续表中的偏移量去寻找,非虚继承的成员变量不需要查找续表。

Animal* animal = liger;
animal->age     = animal->age + 1
     14)获得vbtable (008d6870)中的偏移量14h的值,将其+1。
animal->weight = animal->weight + 1;
     15)将animal->age 偏移+4的memory+1

int p = sizeof(ligher)
     16)指针的大小为4

Lion* lion = liger;
p = lion->age;
     17)通过虚表vbtable (008d6870) 得到lion->age 赋值给局域变量p。lion和liger的地址是一样的。

p = lion->speed;
     18)lion首地址+4
p = lion->weight;
     19)通过虚表得到14,再加上4,得到weight的值。

Tiger* tiger = liger;
     20)tiger = liger + 8,tiger的偏移量为+8,不难发现+8为Tiger虚表Liger::`vbtable' (008d687c)的位置。通过tiger指针引用Animal中的成员变量,都将根据这张虚表。
p = tiger->age;
p = tiger->power;
p = tiger->weight;
     21)和lion中的操作类似,age和weight通过虚表,power直接通过首地址的偏移。通过查找虚表,memory中只需要一份公共基类Animal的拷贝。

     22)虚表中有两个dword,偏移量+4的值为成员变量的偏移量,其他两个都是0 (不知道什么作用,它们在当前代码中没有使用到)

没有使用虚继承的时候,liger的内存布局为:
指针首地址 偏移量 成员变量
Liger   ->
Lion    ->
Animal->
+00h age
  +04h weight
  +08h speed
Tiger->
Animal->
+0Ch age
  +10h weight
  +14h power
  +18h color

Tiger* tiger = liger; 编译器会将tiger指针指向首地址+0Ch的地方。
但是Animal* animal = liger会导致编译错误,因为内存布局中存在两个Animal,我们只能强制指定Animal* animal = (Tiger*)liger。

虚继承的意义:让子类只保存一份公共基类的成员变量,通过vbtable中的偏移查找每个父类的成员变量。



你可能感兴趣的:(C++)