关于C++中菱形继承和虚继承的问题总结

前言

菱形继承是多重继承中跑不掉的,Java拿掉了多重继承,辅之以接口。C++中虽然没有明确说明接口这种东西,但是只有纯虚函数的类可以看作Java中的接口。在多重继承中建议使用“接口”,来避免多重继承中可能出现的各种问题。本文将给大家详细介绍关于C++菱形继承和虚继承的相关内容,分享出来供大家参考学习,话不多说了,来一起看看详细的介绍吧。

继承:

1. 单继承–一个子类只有一个直接父类时称这个继承关系为单继承

2. 多继承–一个子类有两个或以上直接父类时称这个继承关系为多继承

例如下面这两个例子:

例一(单继承):

class A
{
public:
 int _a;
};

class B : public A // B是 子类/派生类, 公有 继承父类/基类 A
{
public:
 int _b;
};

class C : public B //C是 子类/派生类, 公有继承 父类/基类 B
{
public:
 int _c;
};

例二(多继承):

class A
{
public:
 int _a;
};

class B
{
public:
 int _b;
};

class C : public A , public B // 子类C同时公有继承父类A和父类B
{
public:
 int _c;
};

用图很形象的表示一下:

但是在使用过程中,很容易出现一种继承关系叫菱形继承。就好比下面这种继承方式。

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;
};

继承的方式简单画出来就是下面这样:

我们在使用过程中会发现以下缺点:

1、 当我们用D类创建出对象d时,可以访问到_a,但是一旦编译就会出现错误。错误说明为: C2385: 对“_a”的访问不明确。从图中也可以看出,如果用d访问_a时,可能在B类里,也同时可能存在于c类中。这就是所谓的“二义性”;

2、虽然B类和C类都公有继承A,但是在D类公有继承B,C时,存放了两份A类,造成了数据的冗余。

C++针对这种缺陷提出了另外一种继承方式叫做虚继承。

虚继承

C++使用虚拟继承(Virtual Inheritance),解决从不同途径继承来的同名的数据成员在内存中有不同的拷贝造成数据不一致问题,将共同基类设置为虚基类。这时从不同的路径继承过来的同名数据成员在内存中就只有一个拷贝,同一个函数名也只有一个映射。

◇语法:

class 派生类: virtual 基类1,virtual 基类2,…,virtual 基类n

{

…//派生类成员声明

};

在有了虚继承的概念后,我们就可以规避上面的缺点了。

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;
};

当我们使用了虚继承时,继承模型就改变为下面这样:

由于我所使用的是vs2015,在此编译器下对应的处理方式就是这样。将class B 和 class C设置为虚继承后,编译器将class A存放在了最下端,并在B和C类的前四个字节中存放了一个地址,当我们访问过去向下再多看四个字节时就会发现这其中存放了一个数字。而这个数字就类似于“偏移量”,记录了该类的首地址距父类首地址之间的字节差距。比如class B中,我们找到对应数字为14,但是这个数字是16进制,转为10进制为20,在class B的首地址加上20个字节就恰好是class A的首地址,同理class C。

因此在class D访问_a时,就不会产生二义性,_a数据也只存放了一份,解决了之前菱形继承所带来的问题。

但是还存在一个问题:当我们求没有使用虚继承之前的class D的大小,结果是20,但是在使用了虚继承后大小变为24。所以虽然使用虚继承解决数据冗余问题也带来了性能上的损耗。(关于如何计算内存大小,可以参考此链接。)

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • 关于C++对象继承中的内存布局示例详解

    前言 本文给大家介绍的是关于C++对象继承的内存布局的相关内容,分享出来供大家参考学习,在开始之前说明下,关于单继承和多继承的简单概念可参考此文章 以下编译环境均为:WIN32+VS2015 虚函数表 对C++ 了解的人都应该知道虚函数(Virtual Function)是通过一张虚函数表(Virtual Table)来实现的.简称为V-Table.在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承.覆盖的问题,保证其容真实反应实际的函数. 首先先通过一个例子来引入虚函数表,假如现在有三

  • C++中的菱形继承深入分析

    菱形继承 class Person { int _AA; }; class Student:public Person { int _BB; }; class Teacher :public Person { int _CC; }; class Assistant :public Student, public Teacher { int _DD; }; PS: Assistant的对象中存在两份Person成员 菱形继承存在二义性和数据冗余 解决: 使用虚继承 首先不使用虚继承时: #incl

  • C++类继承之子类调用父类的构造函数的实例详解

    C++类继承之子类调用父类的构造函数的实例详解 父类HttpUtil: #pragma once #include <windows.h> #include <string> using namespace std; class HttpUtil { private: LPVOID hInternet; LPVOID hConnect; LPVOID hRequest; protected: wchar_t * mHostName; short mPort; string send

  • C++ 中继承与动态内存分配的详解

    C++ 中继承与动态内存分配的详解 继承是怎样与动态内存分配进行互动的呢?例如,如果基类使用动态内存分配,并重新定义赋值和复制构造函数,这将怎样影响派生类的实现呢?这个问题的答案取决于派生类的属性.如果派生类也使用动态内存分配,那么就需要学习几个新的小技巧.下面来看看这两种情况: 一.派生类不使用new 派生类是否需要为显示定义析构函数,复制构造函数和赋值操作符呢? 不需要! 首先,来看是否需要析构函数,如果没有定义析构函数,编译器将定义一个不执行任何操作的默认构造函数.实际上,派生类的默认构造

  • C语言模式实现C++继承和多态的实例代码

    这个问题主要考察的是C和C++的区别,以及C++中继承和多态的概念. C和C++的区别 C语言是面向过程的语言,而C++是面向对象的过程. 什么是面向对象和面向过程? 面向过程就是分析解决问题的步骤,然后用函数把这些步骤一步一步的进行实现,在使用的时候进行一一调用就行了,注重的是对于过程的分析.面向对象则是把构成问题的事进行分成各个对象,建立对象的目的也不仅仅是完成这一个个步骤,而是描述各个问题在解决的过程中所发生的行为. 面向对象和面向过程的区别? 面向过程的设计方法采用函数来描述数据的操作,

  • 关于C++中菱形继承和虚继承的问题总结

    前言 菱形继承是多重继承中跑不掉的,Java拿掉了多重继承,辅之以接口.C++中虽然没有明确说明接口这种东西,但是只有纯虚函数的类可以看作Java中的接口.在多重继承中建议使用"接口",来避免多重继承中可能出现的各种问题.本文将给大家详细介绍关于C++菱形继承和虚继承的相关内容,分享出来供大家参考学习,话不多说了,来一起看看详细的介绍吧. 继承: 1. 单继承–一个子类只有一个直接父类时称这个继承关系为单继承 2. 多继承–一个子类有两个或以上直接父类时称这个继承关系为多继承 例如下面

  • C++详细讲解继承与虚继承实现

    目录 继承的概念及定义 概念: 定义: 继承关系和访问限定符 总结 基类和派生类对象赋值转换 继承中的作用域 派生类的默认成员函数 继承与友元 继承与静态成员 复杂的菱形继承及菱形虚拟继承 虚继承原理 继承的总结 继承的概念及定义 概念: 继承机制是面向对象程序设计为了提高代码复用率的一种手段,它可以保持原类特性的基础上进行拓展,简单来说继承是类层次的复用. 接下来我们来看一个简单的继承 class Person { public: void Print() { cout<<"nam

  • c++ 虚继承,多继承相关总结

    看这一篇文章之前强烈建议先看以下我之前发布的 虚指针,虚函数剖析 例1: 以下代码输出什么? #include <iostream> using namespace std; class A { protected: int m_data; public: A(int data = 0) {m_data=data;} int GetData() { return doGetData(); } virtual int doGetData() { return m_data; } }; class

  • 关于C++虚继承的内存模型问题

    1.前言 C++虚继承的内存模型是一个经典的问题,其具体实现依赖于编译器,可能会出现较大差异,但原理和最终的目的是大体相同的.本文将对g++中虚继承的内存模型进行详细解析. 2.多继承存在的问题 C++的多继承是指从多个直接基类中产生派生类的能力,多继承的派生类继承了所有父类的成员.从概念上来讲这是非常简单的,但是多个基类的相互交织可能会带来错综复杂的设计问题,命名冲突就是不可回避的一个,比如典型的是菱形继承,如图2-1所示: 图2-1 菱形继承 在图2-1中,类A派生出类B和类C,类D继承自类

  • C++ 继承,虚继承(内存结构)详解

    目录 普通的公有继承 多重继承 虚继承 虚继承(菱形继承) 总结 普通的公有继承 class test1 { public: test1(int i) :num1(i) {} private: int num1; }; class test2 : public test1 { public: test2(int i,int j) : test1(i), num2(j) { } private: int num2; }; void main() { test2 t2(1,2); } (test2内

  • 详解C++中单继承与多继承的使用

    目录 前言 1.继承的概念和定义 (1)继承的概念 (2)继承的定义方法 (2)继承后子类的成员类型 2.基类与派生类的赋值转换 (1)派生类赋值给基类 (2)基类给派生类 3.继承中的作用域 (1)隐藏的概念 (2)例题 4.派生类的默认成员函数 (1)默认生成的成员函数 (2)自己写 5.友元与静态成员 6.多继承 (1)概念 (2)复杂的菱形继承 (3)虚继承解决菱形继承问题 (4)虚继承的原理 7.继承与组合 (1)两者区别 (2)继承与组合的区别 (3)使用情况 8.总结 前言 C++

  • C++中菱形继承的解释与处理详解

    封装,继承,多态.这是C++语言的三大特性,而每次在谈到继承时我们不可避免的要谈到一个很重要的问题——菱形继承. 派生类继承父类,同时也会继承父类中的所有成员副本,但如果在继承时一个基类同时被两个子类继承,然后一个新类又分别由上面的两个子类派生出来.这样从某种程度来说就形成了C++中的菱形继承,也可以叫做钻石继承,具体的继承形式如下图所示: 在上面的类图说,Left和Right分别派生子Top,但是Bottom又分别继承了Left和Right.继承关系也可以画成下面的方式,这样就可以更好的理解设

  • 解析C++编程中virtual声明的虚函数以及单个继承

    虚函数 虚函数是应在派生类中重新定义的成员函数. 当使用指针或对基类的引用来引用派生的类对象时,可以为该对象调用虚函数并执行该函数的派生类版本. 虚函数确保为该对象调用正确的函数,这与用于进行函数调用的表达式无关. 假定基类包含声明为 virtual 的函数,并且派生类定义了相同的函数. 为派生类的对象调用派生类中的函数,即使它是使用指针或对基类的引用来调用的. 以下示例显示了一个基类,它提供了 PrintBalance 函数和两个派生类的实现 // deriv_VirtualFunctions

  • C++虚继承的实现原理由内存布局开始讲起

    目录 准备工作 虚继承的内存分布情况 准备工作 1.VS2012使用命令行选项查看对象的内存布局 微软的Visual Studio提供给用户显示C++对象在内存中的布局的选项:/d1reportSingleClassLayout.使用方法很简单,直接在[工具(T)]选项下找到“Visual Studio命令提示(C)”后点击即可.切换到cpp文件所在目录下输入如下的命令即可 c1 [filename].cpp /d1reportSingleClassLayout[className] 其中[fi

  • C++多重继承与虚继承分析

    本文以实例形式较为全面的讲述了C++的多重继承与虚继承,是大家深入学习C++面向对象程序设计所必须要掌握的知识点,具体内容如下: 一.多重继承 我们知道,在单继承中,派生类的对象中包含了基类部分 和 派生类自定义部分.同样的,在多重继承(multiple inheritance)关系中,派生类的对象包含了每个基类的子对象和自定义成员的子对象.下面是一个多重继承关系图: class A{ /* */ }; class B{ /* */ }; class C : public A { /* */ }

随机推荐