1 单继承

如果我们定义了派生类

  1. class Derive : public Base
  2. {
  3. public:
  4. Derive(int d) :Base(1000), DeriveI(d){};
  5. //overwrite父类虚函数
  6. virtual void print(void){ cout << "Drive::Drive_print()" ; }
  7. // Derive声明的新的虚函数
  8. virtual void Drive_print(){ cout << "Drive::Drive_print()" ; }
  9. virtual ~Derive(){}
  10. private:
  11. int DeriveI;
  12. };

继承类图为:
image.png
一个派生类如何在机器层面上塑造其父类的实例呢?
在C++对象模型中,对于一般继承(这个一般是相对于虚拟继承而言)

  • 若子类重写(overwrite)了父类的虚函数,则子类虚函数将覆盖虚表中对应的父类虚函数(注意子类与父类拥有各自的一个虚函数表)
  • 若子类并无overwrite父类虚函数,而是声明了自己新的虚函数,则该虚函数地址将扩充到虚函数表最后(在vs中无法通过监视看到扩充的结果,不过我们通过取地址的方法可以做到,子类新的虚函数确实在父类虚函数表末端)。

2 继承下的对象模型 - 图2
我们使用代码来验证以上模型

  1. typedef void(*Fun)(void);
  2. int main()
  3. {
  4. Derive d(2000);
  5. //[0]
  6. cout << "[0]Base::vptr";
  7. cout << "\t地址:" << (int *)(&d) << endl;
  8. //vprt[0]
  9. cout << " [0]";
  10. Fun fun1 = (Fun)*((int *)*((int *)(&d)));
  11. fun1();
  12. cout << "\t地址:\t" << *((int *)*((int *)(&d))) << endl;
  13. //vprt[1]析构函数无法通过地址调用,故手动输出
  14. cout << " [1]" << "Derive::~Derive" << endl;
  15. //vprt[2]
  16. cout << " [2]";
  17. Fun fun2 = (Fun)*((int *)*((int *)(&d)) + 2);
  18. fun2();
  19. cout << "\t地址:\t" << *((int *)*((int *)(&d)) + 2) << endl;
  20. //[1]
  21. cout << "[2]Base::baseI=" << *(int*)((int *)(&d) + 1);
  22. cout << "\t地址:" << (int *)(&d) + 1;
  23. cout << endl;
  24. //[2]
  25. cout << "[2]Derive::DeriveI=" << *(int*)((int *)(&d) + 2);
  26. cout << "\t地址:" << (int *)(&d) + 2;
  27. cout << endl;
  28. getchar();
  29. }

运行结果:
2 继承下的对象模型 - 图3
这个结果与我们的对象模型符合。

2 多继承

一般的多重继承

单继承中(一般继承),子类会扩展父类的虚函数表。在多继承中,子类含有多个父类的子对象,该往哪个父类的虚函数表扩展呢?当子类overwrite了父类的函数,需要覆盖多个父类的虚函数表吗?

  • 子类的虚函数被放在声明的第一个基类的虚函数表中
  • overwrite时,所有基类的print()函数都被子类的print()函数覆盖。保证了父类指针指向子类对象时,总是能够调用到真正的函数
  • 内存布局中,父类按照其声明顺序排列 ```cpp class Base { public:

    Base(int i) :baseI(i){}; virtual ~Base(){}

    int getI(){ return baseI; }

    static void countI(){};

    virtual void print(void){ cout << “Base::print()”; }

private:

  1. int baseI;
  2. static int baseS;

}; class Base_2 { public: Base_2(int i) :base2I(i){}; virtual ~Base_2(){} int getI(){ return base2I; } static void countI(){}; virtual void print(void){ cout << “Base_2::print()”; }

private:

  1. int base2I;
  2. static int base2S;

};

class Drive_multyBase :public Base, public Base_2 { public: Drive_multyBase(int d) :Base(1000), Base_2(2000) ,Drive_multyBaseI(d){};

  1. virtual void print(void){ cout << "Drive_multyBase::print" ; }
  2. virtual void Drive_print(){ cout << "Drive_multyBase::Drive_print" ; }

private: int Drive_multyBaseI; };

  1. 继承类图为:<br />![](https://cdn.nlark.com/yuque/0/2021/png/690827/1610008253799-4a8fea8a-71d5-496f-8820-647c2d113880.png#align=left&display=inline&height=589&margin=%5Bobject%20Object%5D&originHeight=589&originWidth=538&size=0&status=done&style=none&width=538)<br />此时Drive_multyBase 的对象模型是这样的:<br />![](https://cdn.nlark.com/yuque/0/2021/png/690827/1610008253803-d804cbbf-e0f6-4f2c-a2a2-b5662be26578.png#align=left&display=inline&height=681&margin=%5Bobject%20Object%5D&originHeight=681&originWidth=1703&size=0&status=done&style=none&width=1703)<br />我们使用代码验证:
  2. ```cpp
  3. typedef void(*Fun)(void);
  4. int main()
  5. {
  6. Drive_multyBase d(3000);
  7. //[0]
  8. cout << "[0]Base::vptr";
  9. cout << "\t地址:" << (int *)(&d) << endl;
  10. //vprt[0]析构函数无法通过地址调用,故手动输出
  11. cout << " [0]" << "Derive::~Derive" << endl;
  12. //vprt[1]
  13. cout << " [1]";
  14. Fun fun1 = (Fun)*((int *)*((int *)(&d))+1);
  15. fun1();
  16. cout << "\t地址:\t" << *((int *)*((int *)(&d))+1) << endl;
  17. //vprt[2]
  18. cout << " [2]";
  19. Fun fun2 = (Fun)*((int *)*((int *)(&d)) + 2);
  20. fun2();
  21. cout << "\t地址:\t" << *((int *)*((int *)(&d)) + 2) << endl;
  22. //[1]
  23. cout << "[1]Base::baseI=" << *(int*)((int *)(&d) + 1);
  24. cout << "\t地址:" << (int *)(&d) + 1;
  25. cout << endl;
  26. //[2]
  27. cout << "[2]Base_::vptr";
  28. cout << "\t地址:" << (int *)(&d)+2 << endl;
  29. //vprt[0]析构函数无法通过地址调用,故手动输出
  30. cout << " [0]" << "Drive_multyBase::~Derive" << endl;
  31. //vprt[1]
  32. cout << " [1]";
  33. Fun fun4 = (Fun)*((int *)*((int *)(&d))+1);
  34. fun4();
  35. cout << "\t地址:\t" << *((int *)*((int *)(&d))+1) << endl;
  36. //[3]
  37. cout << "[3]Base_2::base2I=" << *(int*)((int *)(&d) + 3);
  38. cout << "\t地址:" << (int *)(&d) + 3;
  39. cout << endl;
  40. //[4]
  41. cout << "[4]Drive_multyBase::Drive_multyBaseI=" << *(int*)((int *)(&d) + 4);
  42. cout << "\t地址:" << (int *)(&d) + 4;
  43. cout << endl;
  44. getchar();
  45. }

运行结果:
2 继承下的对象模型 - 图4

菱形继承

菱形继承也称为钻石型继承或重复继承,它指的是基类被某个派生类简单重复继承了多次。这样,派生类对象中拥有多份基类实例(这会带来一些问题)。为了方便叙述,我们不使用上面的代码了,而重新写一个重复继承的继承层次:
2 继承下的对象模型 - 图5

  1. class B
  2. {
  3. public:
  4. int ib;
  5. public:
  6. B(int i=1) :ib(i){}
  7. virtual void f() { cout << "B::f()" << endl; }
  8. virtual void Bf() { cout << "B::Bf()" << endl; }
  9. };
  10. class B1 : public B
  11. {
  12. public:
  13. int ib1;
  14. public:
  15. B1(int i = 100 ) :ib1(i) {}
  16. virtual void f() { cout << "B1::f()" << endl; }
  17. virtual void f1() { cout << "B1::f1()" << endl; }
  18. virtual void Bf1() { cout << "B1::Bf1()" << endl; }
  19. };
  20. class B2 : public B
  21. {
  22. public:
  23. int ib2;
  24. public:
  25. B2(int i = 1000) :ib2(i) {}
  26. virtual void f() { cout << "B2::f()" << endl; }
  27. virtual void f2() { cout << "B2::f2()" << endl; }
  28. virtual void Bf2() { cout << "B2::Bf2()" << endl; }
  29. };
  30. class D : public B1, public B2
  31. {
  32. public:
  33. int id;
  34. public:
  35. D(int i= 10000) :id(i){}
  36. virtual void f() { cout << "D::f()" << endl; }
  37. virtual void f1() { cout << "D::f1()" << endl; }
  38. virtual void f2() { cout << "D::f2()" << endl; }
  39. virtual void Df() { cout << "D::Df()" << endl; }
  40. };

这时,根据单继承,我们可以分析出B1,B2类继承于B类时的内存布局。又根据一般多继承,我们可以分析出D类的内存布局。我们可以得出D类子对象的内存布局如下图:
2 继承下的对象模型 - 图6
D类对象内存布局中,图中青色表示b1类子对象实例,黄色表示b2类子对象实例,灰色表示D类子对象实例。从图中可以看到,由于D类间接继承了B类两次,导致D类对象中含有两个B类的数据成员ib,一个属于来源B1类,一个来源B2类。这样不仅增大了空间,更重要的是引起了程序歧义:

  1. D d;
  2. d.ib =1 ; //二义性错误,调用的是B1的ib还是B2的ib?
  3. d.B1::ib = 1; //正确
  4. d.B2::ib = 1; //正确

尽管我们可以通过明确指明调用路径以消除二义性,但二义性的潜在性还没有消除,我们可以通过虚继承来使D类只拥有一个ib实体。