• 【C++】菱形继承和虚继承


    继承模板类(静态成员)

    1. 两个子类使用同一个类型继承,base和test由于继承object类中的静态成员变量,且都是int类型继承,因此只调用推演一次,num也只初始化一次。
    template<class T>
    class Object
    {
    private:
    	T value;
    public:
    	Object(T x = T()) { value += 1; }
    	static int num;
    };
    template<class T>
    int Object<T>::num = 0;
    class Base:public Object<int>
    {
    public:
    	Base() { num += 1; }
    	void Print() { cout << "Base:" << num << endl; }
    };
    class Test :public Object<int>
    {
    public:
    	Test() { num += 1; }
    	void Print() { cout << "Test:" << num << endl; }
    };
    int main()
    {
    	Base t1, b1;
    	Test t2, b2;
    	t1.Print();  //此时静态成员变量,存放在数据区,模板类的类型为
    	                //在类外进行初始化,且进初始化一次,因此t1,b1,t2,b2操作的都是同一个num
    	t2.Print();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    如下图:
    请添加图片描述
    结果:
    请添加图片描述

    2.base用double类型继承,test用int类型继承,会推演出两个模板,自然而然,在各自的类型模板中,对静态成员变量只初始化一次

    class Base:public Object<double>
    {}class Test :public Object<int>
    {}
    • 1
    • 2
    • 3
    • 4

    请添加图片描述
    结果:
    请添加图片描述

    菱形继承和虚继承

    class Object
    {
    private:
    	int value;
    public:
    	Object(int x = 0) :value(x)
    	{}
    };
    class Base:public Object
    {
    	int num;
    public:
    	Base(int x = 0) :num(x), Object(x + 10) {}
    };
    class Test :public Object
    {
    	int sum;
    public:
    	Test(int x = 0):sum(x),Object(x+10){}
    };
    class Det :public Base, public Test
    {
    	int total;
    public:
    	Det(int x = 0) :total(x), Base(x + 10), Test(x + 20), Object(x + 100) {}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    int main()
    {
    	Det d(0);
    	Base b1 = d;//ok
    	Test t1 = d;//ok
    	Object op = d;//error  op指向d,d继承了Base和Test,所以op不知道指向的是继承base的d还是继承了Test的d,
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    出现的问题:

    在这里插入图片描述
    从下面的对象成员模型构造,可以看出菱形继承有数据冗余和二义性的问题。在Det的对象中Object成员会有两份。
    请添加图片描述

    解决方案:采用虚继承:

    虚继承:主要是通过虚继承,在每个派生类中会有一个虚基类指针(占四个字节),和虚基类表(不占空间);当虚继承的派生类被当做基类再次虚继承,虚基类指针也会被继承。
    (因此每个派生类既可以通过虚基类指针,查找虚基类表找到中的成员)

    注意:

    1. 一个类可以在一个类族中用作虚基类,也可以用作非虚基类。
    2. 在派生类的对象中,同名的虚基类**只产生一个虚基类子对象**,而某个非虚基类产生各自的对象。
    3. 虚基类子对象是由最派生类(最后派生出来的类)的构造函数通过调用虚基类的构造函数进行初始化 (最派生类会先去调用虚基类的构造函数)。
    4. 在派生类的构造函数的成员初始化列表中,必须列出对虚基类构造函数的调用,如果没有列出,则表示使用该虚基类的缺省构造函数。
    5. 在一个成员初始化列表中,同时出现对虚基类和非虚基类构造函数的调用时,虚基类的构造函数先于非虚基类的构造函数执行。
    6. 虚基类并不是在声明基类时声明的,而是在声明派生类是,指定继承方式时声明的。因为一个基类可以在生成一个派生类作为虚基类,而在生成另一个派生类时不作为虚基类。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    //虚基类
    class Object
    {
    private:
    	int value;
    public:
    	Object(int x = 0) :value(x)
    	{}
    };
    class Base:virtual public Object
    {       //存在虚基类指针和虚基类表
    	int num;
    public:
    	Base(int x = 0) :num(x), Object(x + 10) {}
    };
    class Test :virtual public Object
    {            //存在虚基类指针和虚基类表
    	int sum;
    public:
    	Test(int x = 0):sum(x),Object(x+10){}
    };
    class Det :public Base, public Test
    {
    	int total;
    public:
    //虚基类子对象是由最派生类(最后派生出来的类)的构造函数通过调用虚基类的构造函数进行初始化 (最派生类会先去调用虚基类的构造函数)。  优先初始化Object
    	Det(int x = 0) :total(x), Base(x + 10), Test(x + 20), Object(x + 100) {}
    };
    int main()
    {
    	Det d(0);
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    步骤:

    1. 调用Object的构造函数,初始化虚基类,创建隐藏基类对象,保存虚表指针和,虚表
    2. 在初始化Base,这个时候,Base由于虚继承Object,因此Base类中会有一个虚基类指针,和虚基类表。不需要在调动构造函数创建Object的创建隐藏基类对象。
    3. 初始化Test和Base相同。
    4. 对Det进行初始化,创建Det对象d
      如下图:(虚基表指针存放在数据区,存放在对象的开头)
      请添加图片描述
      sizeof(d)
      请添加图片描述
      对于Object基类,Base和Test如何找到它的?
      其实两个派生类里面并不是指针,而是记录了一个偏移量,因为在构造过程中,这一块内存就在一起,不需要拿指针指向某个位置,又不是分散在内存其他地方,书上所说的vbptr是个指针,是为了更好理解。
      根据d的地址:在base中,有一个偏移量 0x14,就是十进制的20,意味着如果要找到Object基类,需要偏移20字节。
      请添加图片描述
      同理:Test里面存放了一个偏移量 0x 0c,即十进制的12,要找到基类Base,需要偏移12个字节。
      请添加图片描述
      在菱形继承中加入虚函数。
    class Object
    {
    private:
    	int value;
    public:
    	Object(int x = 0) :value(x)
    	{}
    	virtual void fun() {}
    	virtual void add() {}
    };
    class Base :public Object
    {
    	int num;
    public:
    	Base(int x = 0) :num(x), Object(x + 10) {}
    	virtual void fun() {}
    };
    class Test :public Object
    {
    	int sum;
    public:
    	Test(int x = 0) :sum(x), Object(x + 10) {}
    	virtual void fun() {}
    	void add() {}
    };
    class Det :public Base, public Test
    {
    	int total;
    public:
    	Det(int x = 0) :total(x), Base(x + 10), Test(x + 20){}
    	void fun() {}
    	void add() {}
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    请添加图片描述
    Object基类中的虚表指针,开始指向Object中的虚表,
    当程序运行到,构建Det对象,因为Det继承了Base和Test类,
    用两种方式去分别继承Base和Test类,因此Object中的虚表指针,在指向Det中的虚表时,有两个,一个继承Base中的虚表,另一个是继承test中的虚表
    请添加图片描述

  • 相关阅读:
    【鸿蒙应用】理财App
    Kafka3.x核心知识速查手册-一、快速上手篇
    66. 加一、Leetcode的Python实现
    JS | “购物车”增、删、改、查的案例
    MVC第三波书店图书类型获取和实现下拉框功能
    推理优化(1)
    HTML+CSS+JS环境保护网页设计期末课程大作业 web前端开发技术 web课程设计 网页规划与设计
    一笔画问题(中国邮递员问题)
    Maven 笔记
    linux 进程组和会话和线程
  • 原文地址:https://blog.csdn.net/weixin_52958292/article/details/127708900