当某类的部分或全部直接基类是从另一个基类共同派生而来时,这直接基类中,从上一级基类继承来的成员就拥有相同的名称,派生类的对象的这些同名成员在内存中同时拥有多个拷贝,同一个函数名有多个映射。可以使用作用域分辨符来唯一标识并分别访问它们。也可以将共同基类设置为虚基类,这时从不同的路径继承过来的同名数据成员在内存中只拥有一个拷贝,同一个函数名也只有一个映射。也就是说虚基类解决了同名成员的唯一标识问题。
-
2015-06-09 21:40:00
转载于:https://www.cnblogs.com/yixianyong/p/4564649.html
更多相关内容 -
C++虚继承和虚基类详解
2020-02-04 23:53:37多继承(Multiple Inheritance)是指从多个直接基类中产生派生类的能力,多继承的派生类继承了所有父类的成员。尽管概念上非常简单,但是多个基类的相互交织可能会带来错综复杂的设计问题,命名冲突就是不可回避的一...1.多继承与菱形继承
多继承(Multiple Inheritance)是指从多个直接基类中产生派生类的能力,多继承的派生类继承了所有父类的成员。尽管概念上非常简单,但是多个基类的相互交织可能会带来错综复杂的设计问题,命名冲突就是不可回避的一个。
多继承时很容易产生命名冲突,即使我们很小心地将所有类中的成员变量和成员函数都命名为不同的名字,命名冲突依然有可能发生,比如典型的是菱形继承,如下图所示:
类 A 派生出类 B 和类 C,类 D 继承自类 B 和类 C,这个时候类 A 中的成员变量和成员函数继承到类 D 中变成了两份,一份来自 A–>B–>D 这条路径,另一份来自 A–>C–>D 这条路径。在一个派生类中保留间接基类的多份同名成员,虽然可以在不同的成员变量中分别存放不同的数据,但大多数情况下这是多余的:因为保留多份成员变量不仅占用较多的存储空间,还容易产生命名冲突。假如类 A 有一个成员变量 a,那么在类 D 中直接访问 a 就会产生歧义,编译器不知道它究竟来自 A -->B–>D 这条路径,还是来自 A–>C–>D 这条路径。
下面是菱形继承的具体实现://间接基类A class A{ protected: int m_a; }; //直接基类B class B: public A{ protected: int m_b; }; //直接基类C class C: public A{ protected: int m_c; }; //派生类D class D: public B, public C{ public: void seta(int a) { m_a = a; } //命名冲突 void setb(int b) { m_b = b; } //正确 void setc(int c) { m_c = c; } //正确 void setd(int d) { m_d = d; } //正确 private: int m_d; }; int main(){ D d; return 0; }
这段代码实现了上图所示的菱形继承,第 22 行代码试图直接访问成员变量 m_a,结果发生了错误,因为类 B 和类 C 中都有成员变量 m_a(从 A 类继承而来),编译器不知道选用哪一个,所以产生了歧义。
为了消除歧义,我们可以在 m_a 的前面指明它具体来自哪个类:
void seta(int a){ B::m_a = a; }
这里使用的是B类的m_a,也可以用C类的:
void seta(int a){ C::m_a = a; }
2.虚继承
为了解决多继承时的命名冲突和冗余数据问题,C++ 提出了虚继承,使得在派生类中只保留一份间接基类的成员。
在继承方式前面加上 virtual 关键字就是虚继承,请看下面的例子:
//间接基类A class A{ protected: int m_a; }; //直接基类B class B: virtual public A{ //虚继承 protected: int m_b; }; //直接基类C class C: virtual public A{ //虚继承 protected: int m_c; }; //派生类D class D: public B, public C{ public: void seta(int a){ m_a = a; } //正确 void setb(int b){ m_b = b; } //正确 void setc(int c){ m_c = c; } //正确 void setd(int d){ m_d = d; } //正确 private: int m_d; }; int main(){ D d; return 0; }
这段代码使用虚继承重新实现了上图所示的菱形继承,这样在派生类 D 中就只保留了一份成员变量 m_a,直接访问就不会再有歧义了。
虚继承的目的是让某个类做出声明,承诺愿意共享它的基类。其中,这个被共享的基类就称为虚基类(Virtual Base Class),本例中的 A 就是一个虚基类。在这种机制下,不论虚基类在继承体系中出现了多少次,在派生类中都只包含一份虚基类的成员。
现在让我们重新梳理一下本例的继承关系,如下图所示:
观察这个新的继承体系,我们会发现虚继承的一个不太直观的特征:必须在虚派生的真实需求出现前就已经完成虚派生的操作。在上图中,当定义 D 类时才出现了对虚派生的需求,但是如果 B 类和 C 类不是从 A 类虚派生得到的,那么 D 类还是会保留 A 类的两份成员。换个角度讲,虚派生只影响从指定了虚基类的派生类中进一步派生出来的类,它不会影响派生类本身。
在实际开发中,位于中间层次的基类将其继承声明为虚继承一般不会带来什么问题。通常情况下,使用虚继承的类层次是由一个人或者一个项目组一次性设计完成的。对于一个独立开发的类来说,很少需要基类中的某一个类是虚基类,况且新类的开发者也无法改变已经存在的类体系。
C++标准库中的 iostream 类就是一个虚继承的实际应用案例。iostream 从 istream 和 ostream 直接继承而来,而 istream 和 ostream 又都继承自一个共同的名为 base_ios 的类,是典型的菱形继承。此时 istream 和 ostream 必须采用虚继承,否则将导致 iostream 类中保留两份 base_ios 类的成员。
3.虚基类成员的可见性
因为在虚继承的最终派生类中只保留了一份虚基类的成员,所以该成员可以被直接访问,不会产生二义性。此外,如果虚基类的成员只被一条派生路径覆盖,那么仍然可以直接访问这个被覆盖的成员。但是如果该成员被两条或多条路径覆盖了,那就不能直接访问了,此时必须指明该成员属于哪个类。
以图2中的菱形继承为例,假设 B 定义了一个名为 x 的成员变量,当我们在 D 中直接访问 x 时,会有三种可能性:
- 如果 B 和 C 中都没有 x 的定义,那么 x 将被解析为 B 的成员,此时不存在二义性。
- 如果 B 或 C 其中的一个类定义了 x,也不会有二义性,派生类的 x 比虚基类的 x 优先级更高。
- 如果 B 和 C 中都定义了 x,那么直接访问 x 将产生二义性问题。
可以看到,使用多继承经常会出现二义性问题,必须十分小心。上面的例子是简单的,如果继承的层次再多一些,关系更复杂一些,程序员就很容易陷人迷魂阵,程序的编写、调试和维护工作都会变得更加困难,因此我不提倡在程序中使用多继承,只有在比较简单和不易出现二义性的情况或实在必要时才使用多继承,能用单一继承解决的问题就不要使用多继承。也正是由于这个原因,C++ 之后的很多面向对象的编程语言,例如 Java、C#、PHP 等,都不支持多继承。
-
虚基类与虚继承
2015-09-04 13:25:11引入虚基类的目的是为了解决类继承过程中产生的二义性问题;这种二义性问题常见于具有菱形继承关系的类中; 比如:有四个类:A、B、C、D;它们之间的继承关系是:B继承A,C继承A,D继承B和C;这就形成了一个菱形的继承关系;...展开全文 -
C++中虚基类与抽象类的简单理解。
2021-03-07 00:37:58虚基类 是相对于它的派生类而言的,它本身可以是一个普通的类。只有它的派生类虚继承它的时候,它才称作虚基类,如果没有虚继承的话,就称为基类。比如类B虚继承于类A,那类A就称作类B的虚基类,如果没有虚继承,那...虚基类 是相对于它的派生类而言的,它本身可以是一个普通的类。
只有它的派生类虚继承它的时候,它才称作虚基类,如果没有虚继承的话,就称为基类。比如类B虚继承于类A,那类A就称作类B的虚基类,如果没有虚继承,那类B就只是类A的基类。
虚继承主要用于一个类继承多个类的情况,避免重复继承同一个类两次或多次。
由类A派生类B和类C,类D又同时继承类B和类C,这时候类D就要用虚继承的方式避免重复继承类A两次。
在C++中引入虚基类的目的在于消除类继承层次中出现的“二义性”问题;所谓“二义性”,以下程序说明
classA {public:voidf()}
classB :publicA { ... }
classC :publicA { ... }
classD :publicB,publicC { ... }
在上述代码中,如果类D的实例去调用继承方法f()时,就会出现“二义性”,因为B和C同时继承了A,各自保留了一份f()(按书上的话,是生成了两个基类子对象); 若将代码稍做改动:
...
classB :virtualpublicA { ... }
classC :virtualpublicA { ... }
...
如此一来,“二义性”就不复存在了!这是一段使用了多重继承的代码,为C++所独有。C#/java是不支持“多重继承”的,因此这点就算做个了解吧
抽象类
1. 是指带有有一个或一个以上的纯虚函数的类。抽象类一般值用于继承,不能定义类对象,但可以定义类指针和引用。
2. 包含有“纯虚函数”的类就是抽象类。它是一种特殊的类,不能直接定义对象,只有通过子类派生并实现它其中的纯虚函数,它才能真正使用!
3. 书本原语“抽象类刻画了一组子类的操作接口的通用语义,这些语义也传给子类。一般而言,抽象类只描述这组子类共同的操作接口,而完整的实现留给子类”。
-
C++引入抽象基类和纯虚函数的作用和目的
2021-04-15 16:02:03为什么要引入抽象基类和纯虚函数?主要目的是为了实现一种接口的效果。 原文链接:https://blog.csdn.net/weibo1230123/article/details/82014322 抽象类是一种特殊的类,它是为了抽象和设计的目的为建立的... -
虚基类的内容
2021-05-18 20:28:33当派生类从多个基类派生,而这些基类又从同一个基类派生,则在访问此共同基类中的成员时,将产生二义性——采用虚基类来解决。 解决方法一:用类名来限定 c1.A::f() 或 c1.B::f() 解决方法二:同名隐藏 在C 中... -
虚函数与虚基类
2017-10-30 10:00:361.虚函数是用于多态中virtual修饰父类函数,确保父类指针调用子类对象时,运行子类函数的。 2.纯虚函数是用来定义接口的,也就是...为了记住以上区别,首先,需要明白为什么要引入虚函数,虚函数的作用是什么,有什 -
[转载]C++继承与内存分配,虚基类指针vbptr和虚函数指针vfptr
2019-05-13 23:56:22为什么这里会出现vbptr,因为虚基类派生出来的类中,虚基类的对象不在固定位置(猜测应该是在内存的尾部),需 要一个中介才能访问虚基类的对象.所以虽然没有virtual函数,子类也需要有一个vbptr,对应的vtable中需要有一项... -
重拾C++之虚函数和虚基类以及抽象类
2021-03-07 00:37:29//尼玛这都能行,被踢大了二、虚函数和一般函数虚函数就是加了vritual关键字的函数,引入虚函数的目的是为了实现多态性(在此为运行时的多态性),即可以通过父类的指针调用子类的对象,从而产生不同的... -
C++ 虚基类表指针字节对齐模型
2016-06-15 11:19:18关于虚基类表指针/虚函数表指针这些类里面的“隐藏成员”在结构里面是如何进行字节对齐的这个问题困惑了我48个小时。虽然网上也有很多关于虚继承、虚函数表、虚基类等内存布局的一些文章,但是基本上谈的都是大致的... -
C++的虚基类
2014-09-22 17:41:02虚基类 当在多条继承路径上有一个公共的基类,在这些路径中的某几条汇合处,这个公共的基类就会产生多个实例(或多个副本),若只想保存这个基类的一个实例,可以将这个公共基类说明为 虚基类 虚基类. ... -
C++ 虚函数&纯虚函数&抽象类&接口&虚基类
2016-10-24 00:01:42多态性就是允许将子类类型的指针赋值给父类类型的指针,多态是通过虚函数实现的。 多态可以让父类的指针有“多种形态”,这是一种泛型技术。(所谓泛型技术,就是试图使用不变的代码来实现可变的算法)。 2. ... -
C++中虚基类的作用
2011-10-03 10:57:15一、首先看虚基类如何定义 父类: class CParent { .... }; 继承类的声明比较特别: 代码中virtual的作用是说明这个CParent是CChild 的虚基类 class CChild : virtua -
纯虚函数、抽象类、虚基类和虚函数的关系
2019-08-26 16:52:506.虚基类 1.虚函数的引入 先看如下程序,程序后有进一步的解释。如果读者对程序不懂请先复习基础知识。 // // VirtualFun.cpp // virtual // // Created by 刘一丁 on 2019/8/26. // Copyright © 2019年 ... -
虚继承---虚基类 菱形继承关系
2013-09-10 15:13:20引入虚基类的目的是为了解决类继承过程中产生的二义性问题;这种二义性问题常见于具有菱形继承关系的类中; 比如:有四个类:A、B、C、D;它们之间的继承关系是:B继承A,C继承A,D继承B和C;这就形成了一个菱形的继承关系;... -
虚继承与虚基类的本质(介绍的非常详细)
2014-09-20 00:17:09虚继承与虚基类的本质 虚继承和虚基类的定义是非常的简单的,同时也是非常容易判断一个继承是否是虚继承 的,虽然这两个概念的定义是非常的简单明确的,但是在C++语言中虚继承作为一个比较生 僻的但是又是绝对... -
多继承、虚基类、虚函数、多态
2017-10-10 16:25:00//如果没有指定继承方式,对与结构体来说就是公有继承,对于类就是私有继承 //公有成员函数称为接口,公有继承,基类的接口成为派生类的接口 //继承与重定义。重定义隐藏了基类的...覆盖(override):要求虚函... -
c++中的虚特性(虚基类、虚函数、纯虚函数)
2021-06-25 10:08:591. 虚基类 1.1 虚基类作用 为了解决多继承时的命名冲突和冗余数据问题,使得派生类中只保留一份间接基类的成员。 其本质是是让某个类做出声明,承诺愿意共享它的基类。其中,这个被共享的基类就称为虚基类... -
虚继承与虚基类的本质(介绍的非常详细)
2017-06-28 13:48:58虚继承与虚基类的本质 虚继承和虚基类的定义是非常的简单的,同时也是非常容易判断一个继承是否是虚继承 的,虽然这两个概念的定义是非常的简单明确的,但是在C++语言中虚继承作为一个比较生 僻的但是又是绝对... -
C++中为什么要引入抽象基类和纯虚函数?
2018-08-24 10:58:57为什么要引入抽象基类和纯虚函数?主要目的是为了实现一种接口的效果。 抽象类是一种特殊的类,它是为了抽象和设计的目的为建立的,它处于继承层次结构的较上层。 ⑴抽象类的定义:带有纯虚函数的类为抽象类。... -
c++中的虚函数、虚基类、类模板
2016-08-15 14:17:37虚函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。 从以上的定义来看,需函数简单的说就是为了让基类指针能够指向派生类中与基类同名的函数而... -
C++ 虚基类表指针字节对齐
2017-09-26 22:07:00在这里我使用了一个空类K,不要被这个东西所迷惑,我使用这个空类的目的主要是为了让它产生虚基类表指针而又不引入虚基类成员变量,这样我就可以少叙述一些关于虚基类成员排放的东西,而将焦点聚集在引入的那个虚... -
虚基类
2010-08-21 09:41:00实际上,引进虚基类的真正目的是为了解决二义性问题。 虚基类说明格式如下: virtual <继承方式><基类名> 其中,virtual是虚类的关键字。虚基类的说明是用在定义派生类时,写在派生类名的后面。例如: ... -
虚基类
2008-10-08 10:47:00在《多继承》中讲过的例子中,由类A,类B1和类B2以及类C组成了类继承的层次结构。在该结构中,类C的对象将包含两个类A的子对象... 虚基类的引入和说明 前面简单地介绍了要引进虚基类的原因。实际上,引进虚基类的真正 -
C++多态、接口和虚基类的深入理解
2016-08-03 22:18:171. 多态 表述一:在面向对象语言中,接口的多种不同实现方式即为多态。多态是指,用父类的指针指向子类的实例(对象),然后...作用是使基类指针或引用统一管理各类对象,是基于虚函数实现。 理解:多态性就是允许将子类 -
纯虚函数与虚基类
2015-02-06 22:08:15纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0” virtual void funtion()=0; 虚函数的定义是:virtual void ...