C++多态对象模型

本文转自https://blog.csdn.net/ling_hun_pang_zi/article/details/81276898

单继承&多继承

前面介绍的是单继承,即一个类是从一个父类派生而来的。但实际上,常常有这样的情况,一个子类有两个或多个父类,子类从两个或多个父类中继承所需的属性。

  • 单继承:一个子类只有一个直接父类。
  • 多继承:一个子类有两个或两个以上的父类。
    这里写图片描述

探索虚函数表

每一个具有虚函数的类都叫做多态类。这个虚函数或者是从基类继承来的,或者是自己新增加的。C++编译器必须为每一个多态类至少创建一个虚函数表,它其实就是一个函数指针数组,其中存放着这个类所有的虚函数的地址及该类的类型信息,其中也包括那些继承但未被改写的虚函数。

每一个多态对象都有一个隐含的指针成员,它指向所属类型的vtable,这就是vptr。
这里写图片描述
单继承的多态对象模型

class Base
{
public:
     virtual void f1()
     {
          cout << "Base::f1" << endl;
     }

     virtual void f2()
     {
          cout << "Base::f2" << endl;
     }

private:
     int _a;
};

class Derived : public Base
{
public:
     virtual void f1()
     {
          cout << "Derived::f1" << endl;
     }

     virtual void f3()
     {
          cout << "Derived::f3" << endl;
     }

     void f4()
     {
          cout << "Derive::f4" << endl;
     }

private:
     int _b;
};

这里写图片描述
打印虚函数的地址

typedef void(*FUNC) ();
void PrintVTable(int* VTable)
{
     cout << " 虚表地址>" << VTable << endl;
     for (int i = 0; VTable[i] != 0; ++i)
     {
          printf(" 第%d个虚函数地址 :0X%x,->", i, VTable[i]);
          FUNC f = (FUNC)VTable[i];
          f();
     }
     cout << endl;
}

这里写图片描述
多继承的多态对象模型

class Base1
{
public:
     virtual void f1()
     {
          cout << "Base1::f1" << endl;
     }

     virtual void f2()
     {
          cout << "Base1::f2" << endl;
     }

private:
     int _b1;
};


class Base2
{
public:
     virtual void f1()
     {
          cout << "Base2::f1" << endl;
     }

     virtual void f2()
     {
          cout << "Base2::f2" << endl;
     }

private:
     int _b2;
};

class Derived : public Base1, public Base2
{
public:
     virtual void f1()
     {
          cout << "Derived::f1" << endl;
     }

     virtual void f3()
     {
          cout << "Derived::f3" << endl;
     }

private:
     int _d1;
};

这里写图片描述

我们可以看出:子类的成员函数被放到了第一个父类的表中。

菱形继承
这里写图片描述

class A
{
public:
     int _a;
};

class B : public A
{
public:
     int _b;
};

class C : public A
{
public:
     int _c;
};

class D : public B, public C
{
public:
     int _d;
};

这里写图片描述
菱形虚拟继承—— 解决菱形继承的二义性和数据冗余的问题

在类B和类C继承类A的时候加virtual关键字,就是虚继承。

class A
{
public:
     int _a;
};

class B : virtual public A
{
public:
     int _b;
};

class C : virtual public A
{
public:
     int _c;
};

class D : public B, public C
{
public:
     int _d;
};

这里写图片描述
对上面的代码我们求D和B的大小
这里写图片描述
我们肯定会奇怪为什么是这样,和我们想象的不太一样啊。。。
这里写图片描述
因为在上面的虚继承图示中,我们可以看到内存中显示的B和C里不仅存储了各自的变量,还各存放了一个地址,这个地址是虚基表的地址,指向虚基表,虚基表里面的后四个字节处存储的是距离A的偏移量(在vs中)。表示向下多少字节处为_a成员。( 虚基表为什么首先不存偏移量,而是在存偏移量之前预留了一个0x0000 0000的位置呢? )所以计算B的大小时,里面会除了B里面自身变量的大小4字节、继承的A里的变量的大小4字节、还有存储的虚基表地址的大小4字节,所以是12字节。D的大小也是这样计算的。

菱形虚拟继承(有虚函数覆盖)

class A
{
public:
     virtual void f1()
     {}
public:
     int _a;
};

class B : virtual public A
{
public:
     virtual void f1()
     {}
     virtual void f2()
     {
          cout << "B::f2()" << endl;
     }
public:
     int _b;
};

class C : virtual public A
{
public:
     virtual void f1()
     {}
     virtual void f2()
     {
          cout << "C::f2()" << endl;
     }
public:
     int _c;
};

class D : public B, public C
{
public:
     virtual void f1()
     {
          cout << "D::f1()" << endl;
     }
     virtual void f3()
     {
          cout << "D::f3()" << endl;
     }
public:
     int _d;
};
int main()
{
     D d;
     d.B::_a = 1;
     d.C::_a = 1;
     d._b = 3;
     d._c = 4;
     d._d = 5;
     PrintVTable((int*)((*(int*)&d)));
     PrintVTable((int*)(*((int*)((char*)&d + sizeof(B) - sizeof(A)))));
     PrintVTable((int*)(*((int*)((char*)&d + sizeof(D) - sizeof(A)))));

     return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

这里写图片描述
我们可以看出:
1) 每个父类都有自己的虚表。
2) 子类的成员函数被放到了第一个父类的表中。
3) 内存布局中,其父类布局依次按声明顺序排列。
4) 每个父类的虚表中的f1()函数都被覆盖成了子类的f1()。这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

int main()
{
     D d;
     d.B::_a = 1;
     d.C::_a = 1;
     d._b = 3;
     d._c = 4;
     d._d = 5;

     cout << sizeof(D) << endl;//36
     cout << sizeof(B) << endl;//20

     return 0;
}

计算D的大小:有变量的大小,还有虚表指针和虚基表指针的大小,从上图可以看出,共36字节。

总结:
虚基表——偏移量 ——虚继承解决菱形继承的二义性和数据冗余
虚表——虚函数重写——虚函数表——实现多态

posted @ 2020-11-29 08:38  crazy_machine  阅读(87)  评论(0编辑  收藏  举报