`

虚继承和虚基类

    博客分类:
  • C++
阅读更多
   虚继承和虚基类的定义是非常的简单的,同时也是非常容易判断一个继承是否是虚继承
的,虽然这两个概念的定义是非常的简单明确的,但是在C++语言中虚继承作为一个比较生
僻的但是又是绝对必要的组成部份而存在着,并且其行为和模型均表现出和一般的继承体系
之间的巨大的差异(包括访问性能上的差异),现在我们就来彻底的从语言、模型、性能和
应用等多个方面对虚继承和虚基类进行研究。
    首先还是先给出虚继承和虚基类的定义。
    虚继承:在继承定义中包含了virtual关键字的继承关系;
    虚基类:在虚继承体系中的通过virtual继承而来的基类,需要注意的是:
    struct CSubClass : public virtual CBase {};
   其中CBase称之为CSubClass的虚基类,而不是说CBase就是个虚基类,因为CBase还可以不不是虚继承体系中的基类。
    有了上面的定义后,就可以开始虚继承和虚基类的本质研究了,下面按照语法、语义、
模型、性能和应用五个方面进行全面的描述。
1. 语法
   语法有语言的本身的定义所决定,总体上来说非常的简单,如下:
           struct CSubClass : public virtual CBaseClass {};
   其中可以采用public、protected、private三种不同的继承关键字进行修饰,只要确保包含virtual就可以了,这样一来就形成了虚继承体系,同时CBaseClass就成为了CSubClass的虚基类了。
   其实并没有那么的简单,如果出现虚继承体系的进一步继承会出现什么样的状况呢?
       如下所示:
            /*
             * 带有数据成员的基类
             */
            struct CBaseClass1
            {
                CBaseClass1( size_t i ) : m_val( i ) {}
           
                size_t m_val;
            };
            /*
             * 虚拟继承体系
             */
            struct CSubClassV1 : public virtual CBaseClass1
            {
                CSubClassV1( size_t i ) : CBaseClass1( i ) {}
            };           
            struct CSubClassV2 : public virtual CBaseClass1
            {
                CSubClassV2( size_t i ) : CBaseClass1( i ) {}
            };           
            struct CDiamondClass1 : public CSubClassV1, public
CSubClassV2
            {
                CDiamondClass1( size_t i ) : CBaseClass1( i ), CSubClassV1( i ), CSubClassV2( i ) {}
            };           
            struct CDiamondSubClass1 : public CDiamondClass1
            {
                CDiamondSubClass1( size_t i ) : CBaseClass1( i ), CDiamondClass1( i ) {}
            };
   注意上面代码中的CDiamondClass1和CDiamondSubClass1两个类的构造函数初始化列表中的内容。可以发现其中均包含了虚基类CBaseClass1的初始化工作,如果没有这个初始化语句就会导致编译时错误,为什么会这样呢?一般情况下不是只要在CSubClassV1和CSubClassV2中包含初始化就可以了么?要解释该问题必须要明白虚 继承的语义特征,所以参看下面语义部分的解释。
      
2. 语义
   从语义上来讲什么是虚继承和虚基类呢?上面仅仅是从如何在C++语言中书写合法的虚继承类定义而已。首先来了解一下virtual这个关键字在C++中的公共含义,在C++语言中仅仅有两个地方可以使用virtual这个关键字,一个就是类成员虚函数和这里所讨论的虚继承。不要看这两种应用场合好像没什么关系,其实他们在背景语义上具有virtual这个词所代表的共同的含义,所以才会在这两种场合使用相同的关键字。
   那么virtual这个词的含义是什么呢?
   virtual在《美国传统词典[双解]》中是这样定义的:
     adj.(形容词)
1. Existing or resulting in essence or effect though not in actual fact, form, or name:
   实质上的,实际上的:虽然没有实际的事实、形式或名义,但在实际上或效果上存在或产生的;
2. Existing in the mind, especially as a product of the imagination.
Used in literary criticism of text.
    虚的,内心的:在头脑中存在的,尤指意想的产物。用于文学批评中。
    我们采用第一个定义,也就是说被virtual所修饰的事物或现象在本质上是存在的,但是没有直观的形式表现,无法直接描述或定义,需要通过其他的间接方式或手段才能够体现出其实际上的效果。那么在C++中就是采用了这个词意,不可以在语言模型中直接调用或体现的,但是确实是存在可以被间接的方式进行调用或体现的。比如:虚函数必须要通过一种间接的,运行时(而不是编译时)机制才能够激活(调用)的函数,而虚继承也是必须在运行时才能够进行定位访问的一种体制。存在,但间接。其中关键就在于存在、间接和共享这三种特征。
对于虚函数而言,这三个特征是很好理解的,间接性表明了他必须在运行时根据实际的对象来完成函数寻址,共享性表象在基类会共享被子类重载后的虚函数,其实指向相同的函数入口。
    对于虚继承而言,这三个特征如何理解呢?存在即表示虚继承体系和虚基类确实存在,间接性表明了在访问虚基类的成员时同样也必须通过某种间接机制来完成(下面模型中会到),共享性表象在虚基类会在虚继承体系中被共享,而不会出现多份拷贝。 那现在可以解释语法小节中留下来的那个问题了,“为什么一旦出现了虚基类,就必须在没有一个继承类中都必须包含虚基类的初始化语句”。由上面的分析可以知道,虚基类是被共享的,也就是在继承体系中无论被继承多少次,对象内存模型中均只会出现一个虚基类的子对象(这和多继承是完全不同的),这样一来既然是共享的那么每一个子类都不会独占,但是总还是必须要有一个类来完成基类的初始化过程(因为所有的对象都必须被初始化,哪怕是默认的),同时还不能重复迳行初始化,那么到底谁应该负责完成初始化呢?C++标准中(也是很自然的)选择在每一次继承子类中都必须书写初始化语句(因为每一次继承子类可能都会用来定义对象),而在最下层 继承子类中实际执行初始化过程。所以上面在每一个继承类中都要书写初始化语句, 但是在创建对象时,而仅仅会在创建对象用的类构造函数中实际的执行初始化语句,其他的初始化语句都会被压制不调用。
      
3. 模型
   为了实现上面所说的三种语义含义,在考虑对象的实现模型(也就是内存模型)时就很自然了。在C++中对象实际上就是一个连续的地址空间的语义代表,我们来分析虚继承下的内存模型。
3.1. 存在
    也就是说在对象内存中必须要包含虚基类的完整子对象,以便能够完成通过地址完成对象的标识。那么至于虚基类的子对象会存放在对象的那个位置(头、中间、尾部)则由各个编译器选择,没有差别。(在VC8中无论虚基类被声明在什么位置,虚基类的子对象都会被放置在对象内存的尾部)
3.2. 间接
    间接性表明了在直接虚基承子类中一定包含了某种指针(偏移或表格)来完成通过子类访问虚基类子对象(或成员)的间接手段(因为虚基类子对象是共享的,没有确定关系),至于采用何种手段由编译器选择。(在VC8中在子类中放置了一个虚基类指针vbc,该指针指向虚函数表中的一个slot,该slot中存放则虚基类子对象的偏移量的负值,实际上就是个以补码表示的int类型的值,在计算虚基类子对象首地址时,需要将该偏移量取绝对值相加,这个主要是为了和虚表中只能存放虚函数地址这一要求相区别,因为地址是原码表示的无符号int类型的值)
3.3. 共享
    共享表明了在对象的内存空间中仅仅能够包含一份虚基类的子对象,并且通过某种间接的机制来完成共享的引用关系。在介绍完整个内容后会附上测试代码,体现这些内容。
4. 性能
    由于有了间接性和共享性两个特征,所以决定了虚继承体系下的对象在访问时必然会在时间和空间上与一般情况有较大不同。
4.1. 时间
    在通过继承类对象访问虚基类对象中的成员(包括数据成员和函数成员)时,都必须通过某种间接引用来完成,这样会增加引用寻址时间(就和虚函数一样),其实就是调整this指针以指向虚基类对象,只不过这个调整是运行时间接完成的。(在VC8中通过打开汇编输出,可以查看*.cod文件中的内容,在访问虚基类对象成员时会形成三条mov间接寻址语句,而在访问一般继承类对象时仅仅只有一条mov常量直接寻址语句)
4.2. 空间
   由于共享所以不同在对象内存中保存多份虚基类子对象的拷贝,这样较之多继承节省空间。
5. 应用
    谈了那么多语言特性和内容,那么在什么情况下需要使用虚继承,而一般应该如何使用呢?这个问题其实很难有答案,一般情况下如果你确性出现多继承没有必要,必须要共享基类子对象的时候可以考虑采用虚继承关系(C++标准ios体系就是这样的)。由于每一个继承类都必须包含初始化语句而又仅仅只在最底层子类中调用,这样可能就会使得某些上层子类得到的虚基类子对象的状态不是自己所期望的(因为自己的初始化语句被压制了),所以一般建议不要在虚基类中包含任何数据成员(不要有状态),只可以作为接口类来提供。
分享到:
评论

相关推荐

    虚基类的使用实例

    - `level1` 和 `level2` 类分别继承自 `base1` 和 `base2`,其中 `level1` 和 `level2` 都使用了虚继承的方式继承 `base1`,以避免多重继承带来的基类成员重复问题。 - `toplevel` 类继承自 `level1` 和 `level2`...

    多继承和虚基类PPT

    "多继承和虚基类PPT" 本资源总结了C++中的多继承和虚基类的概念和应用。多继承是指一个派生类可以继承多个基类的成员,包括数据成员和函数成员。虚基类是解决多继承中出现的命名冲突和继承路径复杂性的机制。 多...

    C++ 虚基类 继承 多态示例

    在C++编程语言中,虚基类、继承和多态是面向对象编程的重要概念,它们为构建复杂软件系统提供了灵活性和可扩展性。本示例程序通过计算不同形状(正方形、矩形、三角形和圆形)的面积,展示了这些核心概念的实际应用...

    虚基类与虚函数

    虚基类与虚函数 虚基类的概念 在C++语言中,一个类不能被多次说明为一个派生类的直接基类,但可以不止一次地成为间接基类。这就导致了一些问题。为了方便 说明,先介绍多继承的“类格”表示法。

    C++ 课程作业 继承与派生 (motorcycle类设计(虚基类))

    从bicycle和motorcar派生出motorcycle,观察虚基类对继承的影响。 定义一个motorcycle的对象,分别调用run()和stop(),观察构造/析构函数的调用情况。 注意:构造函数和析构函数中均为cout语句,说明哪个构造/析构...

    C++经典资料\虚基类与虚函数

    总结来说,虚基类和虚函数是C++中解决多继承问题和实现动态多态的重要工具。虚基类确保了基类在派生层次结构中只有一个实例,避免了二义性;而虚函数则允许子类重写父类的功能,实现了基于对象实际类型而非定义类型...

    多继承与虚基类

    1, 设计一个人员基类person类,包括描述基本信息的数据成员,提供基本操作的函数成员以及析构函数和不同形式的构造函数

    数据结构 C++ 虚函数与虚基类 5个.rar

    在C++编程语言中,数据...总的来说,C++的虚函数和虚基类提供了强大的多态性和继承解决方案,让程序员能够创建复杂的面向对象系统,而无需担心类型转换的困扰。理解并熟练掌握这些特性,对于提升C++编程能力至关重要。

    虚基类 虚函数成员 虚析构函数

    在C++编程语言中,虚基类、虚函数成员和虚析构函数是面向对象编程中的关键概念,它们对于理解和实现多态性至关重要。多态性允许我们编写更灵活、可扩展的代码,使得程序能处理多种不同类型的对象。下面将详细解释这...

    DLL虚基类的方式导出类

    5. **异常安全**:考虑到DLL边界上的异常可能不会跨过,所以需要确保导出的虚基类和其派生类具有良好的异常安全性。 6. **类型兼容性**:确保DLL和应用程序使用的编译器版本相同或兼容,因为不同编译器可能对虚表...

    C++多继承与虚基类

    这种机制使得多继承的类可以通过虚基类实现单一继承的效果,确保代码的正确性和效率。 在实际编程中,使用虚基类需要注意以下几点: 1. 虚基类的构造函数和析构函数应该是公有的,因为它们需要在子类的构造和析构...

    多重继承--虚基类工程代码

    且构造函数和析构函数的调用顺序和单继承是一样的,先调用基类构造函数,再调用对象成员的构造函数,最后调用派生类的构造函数。那么处于同一层次的各个基类构造函数的调用顺序是取决于声明派生类时所指定的各个基类...

    vs2008 多重继承虚基类的简单应用

    在C++编程中,多重继承和虚基类是两种重要的特性,它们允许一个类可以从多个父类中继承属性和行为。Visual Studio 2008(VS2008)作为一个强大的开发环境,支持这些高级的面向对象特性,使得开发者能够创建复杂的...

    C++ 虚继承对基类构造函数调用顺序的影响

    继承作为面向对象编程的一种基本特征,其使用频率...  假设derived 虚继承自base类,那么derivd与base是一种“has a”的关系,即derived类有一个指向base类的vptr。(貌似有些牵强!某些编译器确实如此)  因此虚

    详解虚继承

    编译器会在运行时为虚继承的对象维护一个指向虚基类的指针或引用,从而使得每个派生类对象都共享同一份虚基类的数据成员。这种方式避免了多重继承时的数据冗余问题。 #### 五、性能 由于虚继承需要额外的数据结构...

    继承派生虚基类.rar

    这可能包括显示学生信息、执行特定的操作(如注册、缴费等),以及测试虚基类和多继承的正确性。 此外,这个实验可能还会涉及到C++的访问控制(public, private, protected)、构造函数与析构函数的使用、成员函数...

    虚基类与虚函数PPT学习教案.pptx

    虚基类和虚函数是C++面向对象编程中的重要概念,它们主要解决多继承中可能出现的问题,特别是关于共享基类实例和二义性问题。在本篇PPT学习教案中,我们将深入探讨这两个主题。 首先,让我们理解虚基类的概念。在多...

Global site tag (gtag.js) - Google Analytics