精华内容
下载资源
问答
  • C++中 引入虚基类的作用
    2015-06-09 21:40:00
    当某类的部分或全部直接基类是从另一个基类共同派生而来时,这直接基类中,从上一级基类继承来的成员就拥有相同的名称,派生类的对象的这些同名成员在内存中同时拥有多个拷贝,同一个函数名有多个映射。可以使用作用域分辨符来唯一标识并分别访问它们。也可以将共同基类设置为虚基类,这时从不同的路径继承过来的同名数据成员在内存中只拥有一个拷贝,同一个函数名也只有一个映射。也就是说虚基类解决了同名成员的唯一标识问题。

    转载于:https://www.cnblogs.com/yixianyong/p/4564649.html

    更多相关内容
  • 多继承(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;这就形成了一个菱形的继承关系;...

    虚继承就是虚基类的使用;
    引入虚基类的目的是为了解决类继承过程中产生的二义性问题;这种二义性问题常见于具有菱形继承关系的类中;
    比如:有四个类:A、B、C、D;它们之间的继承关系是:B继承A,C继承A,D继承B和C;这就形成了一个菱形的继承关系;具有这种继承关系的图叫做有向无环图;
    那么类D就有两条继承路径:D-->B-->A和D-->C-->A;而类A是派生类D的两条继承路径上的公共基类,那么这个公共基类就会在派生类D的对象中产生多个基类子对象;这个时候在引用派生类D的对象时,就会产生明显的二义性;要解决这个二义性,就必须将这个基类设定为虚基类;

    虚基类的定义格式如下:
    class <派生类>: virtual <继承方式> <基类名>{};

    例如:
    class A:
    {
      public:
        void fun();
      protected:
        int a;
    };

    class B: virtual public A
    {
      protected:
        int b;
    };

    class C: virtual pulic A
    {
      protected:
        int c;
    };

    class D: public B, public C
    {
      public:
        int g();
      protected:
        int d;
    };

    这样的话,不同继承路径上的虚基类子对象在派生类中被合并成一个子对象了,这便是虚基类的作用,这样就可以消除合并之前出现的二义性问题;这时在派生类D的对象中只存在一个类A的子对象;

    C++中的二义性检查是在访问权限或类型检查之前进行的;

    注意:
    引进虚基类之后,派生类(子类)的对象中只存在一个虚基类的子对象;当一个类拥有虚基类的时候,编译系统会为这个类的对象定义一个指针成员,并让它指向虚基类的子对象;该指针被称为虚基类指针;这个概念与虚函数表指针不同;在内存中,一般情况下,虚基类子对象在派生类对象中是放置在派生类对象所占内存块的尾部,这一点由编译器来决定的;

    虚基类的构造函数:
    为了初始化虚基类的子对象,派生类的构造函数要调用基类的构造函数.由于派生类的对象中只有一个虚基类子对象,那么就必须要保证虚基类的子对象只能被初始化一次,也就是说,虚基类的构造函数只能被调用一次;由于继承的层次可能会很深,C++规定:把真正创建对象时所指定的类称为是最派生类,虚基类子对象是由最派生类的构造函数通过调用虚基类的构造函数进行初始化的;如果一个派生类有一个直接或间接的虚基类,那么派生类的构造函数的成员初始化列表中必须列出对虚基类构造函数的调用,如果没有列出,则表示使用该虚基类的缺省构造函数来初始化派生类对象中的虚基类子对象;
    从虚基类直接或间接继承的派生类的成员初始化列表中必须列出对该虚基类构造函数的调用,但是只有真正用于创建对象的那个最派生类的构造函数才会真正调用虚基类的构造函数,而该派生类的基类中所列出的对这个虚基类的构造函数的调用在实际执行中被忽略,这样就保证对虚基类的子对象只初始化一次;
    C++又规定:在一个成员初始化列表中同时出现对虚基类和非虚基类构造函数的调用时,则虚基类的构造函数先于非虚基类的构造函数被执行;也就是说,执行虚基类构造函数的优先级要高于执行非虚基类构造函数的优先级;
    在需要使用虚基类的场合,由于每一个继承类都必须包含初始化语句,而这些初始化语句仅仅只在最底层子类(最派生类)中才实际调用;这样就会使得某些上层子类得到的虚基类子对象的状态不是自己所期望的(因为自己的初始化语句被压制了/跳过了),所以,一般建议不要在虚基类中包含任何数据成员(不要有状态),只可以作为接口类来提供;
    虚基类实例地址 = 派生类的vbptr + 派生类的vbptr到虚基类实例地址的偏移量;

    菱形继承关系的类图:
    虚继承---虚基类 - 哥哥 - 哥哥

    虚继承---虚基类 - 哥哥 - 哥哥

    虚继承---虚基类 - 哥哥 - 哥哥

    虚继承---虚基类 - 哥哥 - 哥哥虚继承---虚基类 - 哥哥 - 哥哥虚继承---虚基类 - 哥哥 - 哥哥虚继承---虚基类 - 哥哥 - 哥哥虚继承---虚基类 - 哥哥 - 哥哥

     
    阅读(2000) |  评论(0)
    展开全文
  • 虚基类 是相对于它的派生类而言的,它本身可以是一个普通的类。只有它的派生类虚继承它的时候,它才称作虚基类,如果没有虚继承的话,就称为基类。比如类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. 书本原语“抽象类刻画了一组子类的操作接口的通用语义,这些语义也传给子类。一般而言,抽象类只描述这组子类共同的操作接口,而完整的实现留给子类”。

    展开全文
  • 为什么要引入抽象基类和纯虚函数?主要目的是为了实现一种接口的效果。   原文链接: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:36
    1.虚函数是用于多态中virtual修饰父类函数,确保父类指针调用子类对象时,运行子类函数的。 2.纯虚函数是用来定义接口的,也就是...为了记住以上区别,首先,需要明白为什么要引入虚函数,虚函数的作用是什么,有什
  • 为什么这里会出现vbptr,因为虚基类派生出来的类中,虚基类的对象不在固定位置(猜测应该是在内存的尾部),需 要一个中介才能访问虚基类的对象.所以虽然没有virtual函数,子类也需要有一个vbptr,对应的vtable中需要有一项...
  • //尼玛这都能行,被踢大了二、虚函数和一般函数虚函数就是加了vritual关键字的函数,引入虚函数的目的是为了实现多态性(在此为运行时的多态性),即可以通过父类的指针调用子类的对象,从而产生不同的...
  • C++ 虚基类表指针字节对齐模型

    千次阅读 2016-06-15 11:19:18
    关于虚基类表指针/虚函数表指针这些类里面的“隐藏成员”在结构里面是如何进行字节对齐的这个问题困惑了我48个小时。虽然网上也有很多关于虚继承、虚函数表、虚基类等内存布局的一些文章,但是基本上谈的都是大致的...
  • C++的虚基类

    2014-09-22 17:41:02
    虚基类  当在多条继承路径上有一个公共的基类,在这些路径中的某几条汇合处,这个公共的基类就会产生多个实例(或多个副本),若只想保存这个基类的一个实例,可以将这个公共基类说明为   虚基类 虚基类. ...
  • 多态性就是允许将子类类型的指针赋值给父类类型的指针,多态是通过函数实现的。 多态可以让父类的指针有“多种形态”,这是一种泛型技术。(所谓泛型技术,就是试图使用不变的代码来实现可变的算法)。 2. ...
  • C++中虚基类的作用

    千次阅读 2011-10-03 10:57:15
    一、首先看虚基类如何定义 父类: class CParent { .... }; 继承类的声明比较特别: 代码中virtual的作用是说明这个CParent是CChild 的虚基类 class CChild : virtua
  • 6.虚基类 1.虚函数的引入 先看如下程序,程序后有进一步的解释。如果读者对程序不懂请先复习基础知识。 // // VirtualFun.cpp // virtual // // Created by 刘一丁 on 2019/8/26. // Copyright © 2019年 ...
  • 引入虚基类目的是为了解决类继承过程中产生的二义性问题;这种二义性问题常见于具有菱形继承关系的类中; 比如:有四个类:A、B、C、D;它们之间的继承关系是:B继承A,C继承A,D继承B和C;这就形成了一个菱形的继承关系;...
  • 虚继承与虚基类的本质  虚继承和虚基类的定义是非常的简单的,同时也是非常容易判断一个继承是否是虚继承 的,虽然这两个概念的定义是非常的简单明确的,但是在C++语言中虚继承作为一个比较生 僻的但是又是绝对...
  • //如果没有指定继承方式,对与结构体来说就是公有继承,对于类就是私有继承 //公有成员函数称为接口,公有继承,基类的接口成为派生类的接口 //继承与重定义。重定义隐藏了基类的...覆盖(override):要求函...
  • 1. 虚基类 1.1 虚基类作用 为了解决多继承时的命名冲突和冗余数据问题,使得派生类中只保留一份间接基类的成员。 其本质是是让某个类做出声明,承诺愿意共享它的基类。其中,这个被共享的基类就称为虚基类...
  • 虚继承与虚基类的本质  虚继承和虚基类的定义是非常的简单的,同时也是非常容易判断一个继承是否是虚继承 的,虽然这两个概念的定义是非常的简单明确的,但是在C++语言中虚继承作为一个比较生 僻的但是又是绝对...
  • 为什么要引入抽象基类和纯虚函数?主要目的是为了实现一种接口的效果。  抽象类是一种特殊的类,它是为了抽象和设计的目的为建立的,它处于继承层次结构的较上层。  ⑴抽象类的定义:带有纯虚函数的类为抽象类。...
  • c++中的虚函数、虚基类、类模板

    千次阅读 2016-08-15 14:17:37
    函数的作用是允许在派生类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和派生类中的同名函数。 从以上的定义来看,需函数简单的说就是为了让基类指针能够指向派生类中与基类同名的函数而...
  • 在这里我使用了一个空类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:17
    1. 多态 表述一:在面向对象语言中,接口的多种不同实现方式即为多态。多态是指,用父类的指针指向子类的实例(对象),然后...作用是使基类指针或引用统一管理各类对象,是基于函数实现。 理解:多态性就是允许将子类
  • 纯虚函数与虚基类

    千次阅读 2015-02-06 22:08:15
    纯虚函数是在基类中声明的函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0” virtual void funtion()=0; 函数的定义是:virtual void ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 13,496
精华内容 5,398
关键字:

引入虚基类的目的