C++ -- 多态场景下探索C++对象模型

单继承对象模型

1.单继承代码

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

      virtual void fun2()
      {
           cout << "Base::fun2()" << endl;
      }
private:
      int _a;
};

class Derive :public Base
{
public:
      virtual void fun1()  //重写了fun1
      {
           cout << "Derive::fun1()" << endl;
      }

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

      virtual void fun4()
      {
           cout << "Derive::fun4()" << endl;
      }
private:
      int _b;
};
int main()
{
      Base b1;
      Derive d1;
      system("pause");
      return 0;
}

2.监视窗口如下:
这里写图片描述
(1)我们可以发现,Derive的虚表里面重写了Base的虚函数fun1,但是上面的监视窗口并没有显示Derive类里面的虚函数fun3和fun4,我们需要知道这两个函数到底存放在哪里,则需要自己去编写打印虚表的函数;
(2)打印虚表的函数如下:

typedef void(*VFUNC)();

void PrintVFtable(int**table)
{
      for (size_t i = 0; table[i] != 0; ++i)
      {
           printf("[%d] 0x%p ->", i, table[i]);
           VFUNC f = (VFUNC)table[i];
           f();
      }
      cout << endl;
}

(3)则打印出来的虚表如下:
这里写图片描述
可以发现:Derive类重写了Base类的fun1,没有重写fun2,Derive类的fun3和fun4与fun1,fun2放在同一张虚表里。
3.单继承对象模型示意图:
这里写图片描述
总结:在单继承中,子类继承父类时,如果父类含有虚函数,子类会生成一个自己的虚表,并且将父类的虚表里面的内容拷贝至自己的虚表里面,再在自己的虚表的后面添上自己其它的虚函数。

多继承对象模型

1.多继承代码如下:

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

      virtual void func2()  
      {
           cout << "Base1::func2" << endl;
      }
private:    
      int b1;
};

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

      virtual void func2() 
      {
           cout << "Base2::func2" << endl;
      }
private:    
      int b2;
};
class Derive : public Base1, public Base2
{
public:    
      virtual void func1()
      {
           cout << "Derive::func1" << endl;
      }

      virtual void func3()  
      {
           cout << "Derive::func3" << endl;
      }
private:    
      int d1;
};

2.定义一个Derive对象d1,监视窗口如下:
这里写图片描述
(1)通过监视窗口可以看到Derive类继承了两个父类Base1和Base2,Derive对象里面含有两个虚表。
(2)如何证明有两个虚表(通过计算对象d1所占空间的大小)
这里写图片描述

  • 可以看到d1占20个字节,因为Derive继承了Base1和Base2,所以Derive里面会包含一个b1和一个b2(都是int),还包含自身的d1,这一共占12个字节,如果Derive类里只包含一个虚表指针,则一共占16字节,这里占20个字节,所以包含两个虚表指针,即包含两个虚表。

3.打印d1里面的两个虚表(Base1虚表指针位于d1对象的头上4个字节处,Base2虚表指针在对象Base1的后面,即第二张虚表的地址应为d1的地址加上Base1对象的大小)
这里写图片描述
4.多继承对象模型示意图:

总结:在多继承中,子类自己的虚函数放在先继承的虚表里面。

菱形继承对象模型

1.菱形继承的代码如下:

class A
{
public:
      virtual void func1()   
      {
           cout << "A::func1()" << endl;
      }               
      int _a;
};

class B:public A
{
public:
      virtual void func1()
      {
           cout << "B::func1()" << endl;
      }

      virtual void func2()
      {
           cout << "B::func2()" << endl;
      }
      int _b;
};
class C:public A
{
public:
      virtual void func1()
      {
           cout << "C::func1()" << endl;
      }

      virtual void func3()
      {
           cout << "C::fun3()" << endl;
      }
      int _c;
};

class D:public B,public C
{
public:
      virtual void func1()
      {
           cout << "D::func1()" << endl;
      }

      virtual void func4()
      {
           cout << "D::func4()" << endl;
      }
      int _d;
};

2.菱形继承的内存布局:

  • A有一个虚表,由于B继承了A,所以B里面含有一张虚表,且B的虚表指针放在B的头上4个字节处;C继承了A,C里面也有一张虚表,且C的虚表指针放在C头上4字节处;D同时继承了B和C,B和C个有一张虚表,则 D里面含有两张虚表,且D自己的其它虚函数放在先继承的B类的虚表里面。
    这里写图片描述
    3.打印的虚表如下:
    这里写图片描述

菱形虚拟继承对象模型

这里写图片描述

情景1:B和C中除了重写A的虚函数之外没有其它的虚函数

1.在菱形虚拟继承中,如果B,C重写了A的虚函数,则D必须重写A的虚函数,否则会出错。
以下代码D没有重写A的虚函数:

class A
{
public:
      virtual void func1()   
      {
           cout << "A::func1()" << endl;
      }

      int _a;
};

class B:virtual public A
{
public:
      virtual void func1()
      {
           cout << "B::func1()" << endl;
      }

      int _b;
};
class C :virtual public A
{
public:
      virtual void func1()
      {
           cout << "C::fun1()" << endl;
      }
      int _c;
};

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

int main()
{
      D d1;
      system("pause");
      return 0;
}

则会出现如下错误:(因为B重写了A的虚函数,C重写了A的虚函数,但是在菱形虚拟继承中,A是公共的,则A里面的虚表不确定到底重写谁的虚函数,就会出现错误,因此D必须重写A的虚函数,则A的虚表里面存放D重写后的虚函数)
这里写图片描述
2.D中除了重写A的虚函数外没有其它的虚函数对应的对象模型

class A
{
public:
      virtual void func1()   
      {
           cout << "A::func1()" << endl;
      }

      int _a;
};

class B:virtual public A
{
public:
      virtual void func1()
      {
           cout << "B::func1()" << endl;
      }

      int _b;
};
class C :virtual public A
{
public:
      virtual void func1()
      {
           cout << "C::fun1()" << endl;
      }
      int _c;
};

class D:public B,public C
{
public:
      virtual void func1()
      {
           cout << "D::fun1()" << endl;
      }
      int _d;
};

int main()
{
      D d1;
      d1._a = 1;
      d1._b = 2;
      d1._c = 3;
      d1._d = 4;
      system("pause");
      return 0;
}

(1)对象模型如下:(通过内存窗口)
这里写图片描述
(2)总结:在菱形虚拟继承里面,单继承的类(B,C)里面都含有一个虚基表,如果B,C,及子类(D)除了重写A的虚函数外,没有其它的虚函数,则B,C里面都不包含虚表,虚表位于A的位置处。
3.D里面还含有自己的虚函数
①代码如下:

class A
{
public:
      virtual void func1()   
      {
           cout << "A::func1()" << endl;
      }

      int _a;
};

class B:virtual public A
{
public:
      virtual void func1()
      {
           cout << "B::func1()" << endl;
      }

      int _b;
};
class C :virtual public A
{
public:
      virtual void func1()
      {
           cout << "C::fun1()" << endl;
      }
      int _c;
};

class D:public B,public C
{
public:
      virtual void func1()
      {
           cout << "D::fun1()" << endl;
      }

      virtual void func2()
      {
           cout << "D::fun2()" << endl;
      }
      int _d;
};

int main()
{
      D d1;
      d1._a = 1;
      d1._b = 2;
      d1._c = 3;
      d1._d = 4;
      system("pause");
      return 0;
}

②对象模型如下:
这里写图片描述
总结:在菱形虚拟继承里,如果B和C除了重写A的虚函数外没有其它的虚函数,D里面除了重写A的虚函数之外还有其它的虚函数,则D先继承哪个类,哪个类对应的位置处就要重新生成一个虚表,并把D自己的虚函数放进虚表里。

情景2:B和C中除了重写A的虚函数之外还有自己的虚函数)

(1)代码如下:

class A
{
public:
      virtual void func1()   
      {
           cout << "A::func1()" << endl;
      }

      int _a;
};

class B:virtual public A
{
public:
      virtual void func1()
      {
           cout << "B::func1()" << endl;
      }

      virtual void func2()
      {
           cout << "B::func2()" << endl;
      }
      int _b;
};
class C :virtual public A
{
public:
      virtual void func1()
      {
           cout << "C::func1()" << endl;
      }

      virtual void func3()
      {
           cout << "C::fun3()" << endl;
      }
      int _c;
};

class D:public B,public C
{
public:
      virtual void func1()
      {
           cout << "D::func1()" << endl;
      }

      virtual void func4()
      {
           cout << "D::func4()" << endl;
      }
      int _d;
};

(2)菱形虚拟继承的内存布局
①通过对各个类成员变量的赋值可以确定类的分布

      D d1;
      d1._b = 1;
      d1._c = 2;
      d1._d = 3;
      d1._a = 4;

这里写图片描述
②可以发现B和C里面都含有两个地址,A里面含有一个地址;
③再打开一个内存窗口,去观察这些地址里面的内容:
这里写图片描述
可以看出:在含有虚函数覆盖的菱形虚拟继承里面,①B和C里面都包含一个虚基表;②如果B和C都含有自己的虚函数,则B和C都会有自己独立的虚表,而且虚表指针位于对象的头上,接下来才是虚基表指针;③可以看到当B和C都含有虚表时,B和C的虚基表的头上4字节存放的不在是0而是fffffffc,也就是-4(即表示虚表位于当前地址的前面4字节处)。
(3)打印D的虚表(由上面图可以看出D一共包含3个虚表)

int main()
{
      D d1;

      PrintVFtable((int**)(*((int**)&d1)));
      PrintVFtable((int**)(*((int**)((char*)&d1+sizeof(B)-sizeof(A)))));
      PrintVFtable((int**)(*((int**)((char*)&d1 + sizeof(D)-sizeof(A)))));
      system("pause");
      return 0;
}

这里写图片描述
可以看出:D里面重写的fun1放在A的虚表里面,D里面的fun4放在先继承的B的虚表里面,B和C的fun1全部被D改写,改写之后放入A的虚表里面。

菱形虚拟继承总结:

对于菱形虚拟继承,给定类A,B,C,D,(B和C单继承了A,D多继承了B和C)
①如果B和C都重写了A的虚函数,则D也必须重写A的虚函数;(如果只有一个类重写了A的虚函数,则D不必重写);
②如果B、C及D里面除了重写的A的虚函数外没有其它的虚函数,则D的对象模型中:B和C里面没有独立的虚表,D的虚表只位于A处;
③如果B和C里面除了重写的A的虚函数外没有其它的虚函数,而D包含自己的虚函数,则D先继承的类会生成一个虚表且里面存放D的虚函数;
④如果B和C里面除了重写的A的虚函数外还有各自的虚函数,则D对象里位于B和C处还各自包含一个虚表。

猜你喜欢

转载自blog.csdn.net/xu1105775448/article/details/80257385