设为首页 加入收藏

TOP

谈VC++对象模型(四十四)
2012-11-02 08:51:57 】 浏览:25166
Tags:对象 模型

    2.3 单继承
   
    C++(www.cppentry.com)提供继承的目的是在不同的类型之间提取共性。比如,科学家对物种进行分类,从而有种、属、纲等说法。有了这种层次结构,我们才可能将某些具备特定性质的东西归入到最合适的分类层次上,如"怀孩子的是哺乳动物".由于这些属性可以被子类继承,所以,我们只要知道"鲸鱼、人"是哺乳动物,就可以方便地指出"鲸鱼、人都可以怀孩子".那些特例,如鸭嘴兽(生蛋的哺乳动物),则要求我们对缺省的属性或行为进行覆盖。
   
    C++(www.cppentry.com)中的继承语法很简单,在子类后加上":base"就可以了。下面的D继承自基类C.
   
    struct C {
   
    int c1;
   
    void cf();
   
    };
   
    struct D : C {
   
    int d1;
   
    void df();
   
    };
   
    既然派生类要保留基类的所有属性和行为,自然地,每个派生类的实例都包含了一份完整的基类实例数据。在D中,并不是说基类C的数据一定要放在D的数据之前,只不过这样放的话,能够保证D中的C对象地址,恰好是D对象地址的第一个字节。这种安排之下,有了派生类D的指针,要获得基类C的指针,就不必要计算偏移量了。几乎所有知名的C++(www.cppentry.com)厂商都采用这种内存安排。在单继承类层次下,每一个新的派生类都简单地把自己的成员变量添加到基类的成员变量之后。看看上图,C对象指针和D对象指针指向同一地址。
   
    2.4 多重继承
   
    大多数情况下,其实单继承就足够了。但是,C++(www.cppentry.com)为了我们的方便,还提供了多重继承。
   
    比如,我们有一个组织模型,其中有经理类(分任务),工人类(干活)。那么,对于一线经理类,即既要从上级经理那里领取任务干活,又要向下级工人分任务的角色来说,如何在类层次中表达呢?单继承在此就有点力不胜任。我们可以安排经理类先继承工人类,一线经理类再继承经理类,但这种层次结构错误地让经理类继承了工人类的属性和行为。反之亦然。当然,一线经理类也可以仅仅从一个类(经理类或工人类)继承,或者一个都不继承,重新声明一个或两个接口,但这样的实现弊处太多:多态不可能了;未能重用现有的接口;最严重的是,当接口变化时,必须多处维护。最合理的情况似乎是一线经理从两个地方继承属性和行为--经理类、工人类。
   
    C++(www.cppentry.com)就允许用多重继承来解决这样的问题:
   
    struct Manager … { … };
   
    struct Worker … { … };
   
    struct MiddleManager : Manager, Worker { … };
   
    这样的继承将造成怎样的类布局呢?下面我们还是用"字母类"来举例:
   
    struct E {
   
    int e1;
   
    void ef();
   
    };
   
    struct F : C, E {
   
    int f1;
   
    void ff();
   
    };
   
    结构F从C和E多重继承得来。与单继承相同的是,F实例拷贝了每个基类的所有数据。与单继承不同的是,在多重继承下,内嵌的两个基类的对象指针不可能全都与派生类对象指针相同:
   
    F f;
   
    // (void*)&f == (void*)(C*)&f;
   
    // (void*)&f <  (void*)(E*)&f;
   
    译者注:上面那行说明C对象指针与F对象指针相同,下面那行说明E对象指针与F对象指针不同。
   
    观察类布局,可以看到F中内嵌的E对象,其指针与F指针并不相同。正如后文讨论强制转化和成员函数时指出的,这个偏移量会造成少量的调用开销。
   
    具体的编译器实现可以自由地选择内嵌基类和派生类的布局。VC++(www.cppentry.com)按照基类的声明顺序先排列基类实例数据,最后才排列派生类数据。当然,派生类数据本身也是按照声明顺序布局的(本规则并非一成不变,我们会看到,当一些基类有虚函数而另一些基类没有时,内存布局并非如此)。
   
    2.5 虚继承
   
    回到我们讨论的一线经理类例子。让我们考虑这种情况:如果经理类和工人类都继承自"雇员类",将会发生什么?
   
    struct Employee { … };
   
    struct Manager : Employee { … };
   
    struct Worker : Employee { … };
   
    struct MiddleManager : Manager, Worker { … };
   
    如果经理类和工人类都继承自雇员类,很自然地,它们每个类都会从雇员类获得一份数据拷贝。如果不作特殊处理,一线经理类的实例将含有两个雇员类实例,它们分别来自两个雇员基类。如果雇员类成员变量不多,问题不严重;如果成员变量众多,则那份多余的拷贝将造成实例生成时的严重开销。更糟的是,这两份不同的雇员实例可能分别被修改,造成数据的不一致。因此,我们需要让经理类和工人类进行特殊的声明,说明它们愿意共享一份雇员基类实例数据。
   
    很不幸,在C++(www.cppentry.com)中,这种"共享继承"被称为"虚继承",把问题搞得似乎很抽象。虚继承的语法很简单,在指定基类时加上virtual关键字即可。
   
    struct Employee { … };
   
    struct Manager : virtual Employee { … };
   
    struct Worker : virtual Employee { … };
   
    struct MiddleManager : Manager, Worker { … };
   
    使用虚继承,比起单继承和多重继承有更大的实现开销、调用开销。回忆一下,在单继承和多重继承的情况下,内嵌的基类实例地址比起派生类实例地址来,要么地址相同(单继承,以及多重继承的最靠左基类),要么地址相差一个固定偏移量(多重继承的非最靠左基类)。然而,当虚继承时,一般说来,派生类地址和其虚基类地址之间的偏移量是不固定的,因为如果这个派生类又被进一步继承的话,最终派生类会把共享的虚基类实例数据放到一个与上一层派生类不同的偏移量处。请看下例:
   
    struct G : virtual C {
   
    int g1;
   
    void gf();
   
    };
   

                  

首页 上一页 41 42 43 44 45 46 47 下一页 尾页 44/47/47
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇用VC++制作DLL经验 下一篇VC++数据类型

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目