• linkedu视频
  • 平面设计
  • 电脑入门
  • 操作系统
  • 办公应用
  • 电脑硬件
  • 动画设计
  • 3D设计
  • 网页设计
  • CAD设计
  • 影音处理
  • 数据库
  • 程序设计
  • 认证考试
  • 信息管理
  • 信息安全
菜单
linkedu.com
  • 网页制作
  • 数据库
  • 程序设计
  • 操作系统
  • CMS教程
  • 游戏攻略
  • 脚本语言
  • 平面设计
  • 软件教程
  • 网络安全
  • 电脑知识
  • 服务器
  • 视频教程
  • JavaScript
  • ASP.NET
  • PHP
  • 正则表达式
  • AJAX
  • JSP
  • ASP
  • Flex
  • XML
  • 编程技巧
  • Android
  • swift
  • C#教程
  • vb
  • vb.net
  • C语言
  • Java
  • Delphi
  • 易语言
  • vc/mfc
  • 嵌入式开发
  • 游戏开发
  • ios
  • 编程问答
  • 汇编语言
  • 微信小程序
  • 数据结构
  • OpenGL
  • 架构设计
  • qt
  • 微信公众号
您的位置:首页 > 程序设计 >C语言 > C++ 多重继承和虚拟继承对象模型、效率分析

C++ 多重继承和虚拟继承对象模型、效率分析

作者: 字体:[增加 减小] 来源:互联网 时间:2017-05-28

通过本文主要向大家介绍了深度探索c++对象模型,c++对象模型,c++对象的内存模型,深入探索c++对象模型,深入c++对象模型等相关知识,希望对您有所帮助,也希望大家支持linkedu.com www.linkedu.com

一、多态

C++多态通过继承和动态绑定实现。继承是一种代码或者功能的传承共享,从语言的角度它是外在的、形式上的,极易理解。而动态绑定则是从语言的底层实现保证了多态的发生——在运行期根据基类指针或者引用指向的真实对象类型确定调用的虚函数功能!通过带有虚函数的单一继承我们可以清楚的理解继承的概念、对象模型的分布机制以及动态绑定的发生,即可以完全彻底地理解多态的思想。为了支持多态,语言实现必须在时间和空间上付出额外的代价(毕竟没有免费的晚餐,更何况编译器是毫无感情):

1、类实现时增加了virtual table,用来存放虚函数地址;
2、类对象中增加了指向虚函数表的指针vptr,以提供runtime的链接;
3、在类继承层次的构造函数中重复设定vptr的初值,以期待指针指向对应类的virtual table;
4、在类继承层次的析构函数中重复还原vptr的初值;
5、多态发生时(base class指针调用虚函数)需要通过vptr和virtual table表调用对应函数实体,增加了 一层间接性。
第1、2两点是多态带来的空间代价,后面三点则是时间效率上的代价。

二、多重继承和虚拟继承

多重继承具有多个base class,有别于单一继承(提供了一种“自然多态”形式)。单一继承中,基类和派生类具有相同的内存地址,它们之间的转换十分自然不需要编译器的介入。但如果基类中没有虚函数而派生类中有,单一继承的自然多态被打破。这种情况下,派生类转换为基类需要编译器的介入,用以调整this指针地址。多重继承的对象模型较单一继承复杂,根源在于derived class objects和其第二或后继的base class objects之间的“非自然”关系 ,这一点可以从下面的对象模型中看到。派生类和基类之间的非自然多态引起了一个严重的问题(在虚拟继承中也存在):derived class和第二或后继base class之间的转换(不论是对象间的直接转换或者经由其所支持的virtual function机制做转换)需要调整this指针的地址,以使其指向完整正确的class object 。
虚拟继承是一种机制,类通过虚继承指出它所希望共享虚基类的状态,虚基类在派生层次中只有一份实体。相比多重继承,虚拟继承的难点在于既要识别出相同的对象部分又要维持基类和派生类之间的多态关系 。通常情况下,实现虚拟继承时编译器将对象分割为一个不变局部和一个共享局部 。不变局部中的数据,不管后继如何衍化,总是拥有固定的offset,所以这一部分数据可以被直接存取。至于共享局部,所表现的就是virtual base class subobject。这一部分的数据,其位置会因为每次的派生操作而有变化,所以它们只能被间接存取 。各家编译器实现技术之间的差异在于间接存取方法不同。一般的策略就是先安排好派生类的不变部分,然后建立共享部分。虚拟继承base class和derived class之间非自然的多态关系,它们之间相互转换时需要对this指针地址进行调整。由于对virtual base class的支持,虚拟继承带来了额外的负担和模型复杂性。

三、多重继承和虚拟继承对象模型

造成多重继承和虚拟继承较普通单一继承复杂、效率低的本质在于 对象模型内存分布的差异, 这一点从第二部分分析也可以看到。下面示例对比列出了普通单一继承、多重继承以及虚拟继承的对象模型。需要说明的是:C++标准中并没有强制规定base class members和derived class members之间的次序关系,理论上可以自由安排之,但实际上大多数编译器都会基类成员放在前面,但虚拟继承除外。下面也是这种策略,同时把vptr作为类的第一个成员。

基类Base1、Base2以及派生类DerivedSingle、DerivedMulti类定义如下:

class Base1
{
public:
  Base1(void);
  ~Base1(void);
  virtual Base1* clone()const;
protected:
  float data_Base1;
};
</div>
class Base2
{
public:
  Base2(void);
  ~Base2(void);
  virtual void mumble();
  virtual Base2* clone()const;
protected:
  float data_Base2;
};
</div>
class DerivedSingle: public Base1
{
public:
  DerivedSingle(void);
  virtual ~DerivedSingle(void);
  virtual DerivedSingle* clone() const;
protectd:
  float data_DerivedSingle;
};
</div>
class DerivedMulti :public Base1, public Base2
{
public:
  DerivedMulti(void);
  virtual ~DerivedMulti(void);
  virtual DerivedMulti* clone() const;
protected:
  float data_DerivedMulti;
};
</div>

对象模型如下,虚拟继承和单一继承类结构相同,只是继承改成了虚拟继承。

单一继承:

多重继承:

虚拟继承:

为了保证memberwise复制的正确性(否则基类子对象复制给派生类时会发生错误),C++中保证“基类子对象在派生类中的原样性 ”。

单一继承的对象模型呈现了一种“自然多态”的形式,基类和派生类之间的转换十分自然简单。然而多重继承有多个基类,对象有多个vptr指针,对于第二个或后继基类和派生类之间的转换需要地址调整,以指向完整的基类子对象。

虚拟继承中,为了记住和共享虚拟基类,需要在类中添加指向该基类的指针。从上面的虚拟继承对象模型中可以看到,虽然和单一继承有相同的类层次结构,但虚拟继承打破了单一继承的“自然多态”形式,基类和派生类之间的转换需要调整this指针的地址。如果是虚拟多重继承,则虚拟基类/后继基类和派生类之间的转换需要this指针地址调整 。

一般规则,多重继承经由指向“第二个或者后继base class”的指针(引用)来调用derived class virtual function,该操作所连带的“必要的this指针调整”操作,必须在执行期完成,也就是说offset的大小、以及吧offset加到this指针上头的那一小段程序代码,必须有编译器在某个地方插入。为了实现this指针调整引入thunk技术,所谓thunk是一小段assembly代码,用来以适当的offset值调整this指针,并跳到virtual函数去。Thunk技术允许virtual table slot继续内含一个简单的指针,因此多重继承不需要额外任何空间上的额外负担。Slots中的地址可以直接指向virtual function,也可以指向一个相关的thunk(如果需要调整this指针)。调整this指针的第二个额外负担就是,由于两中不同的可能:(1)经由derived class(或者第一个base class)调用,(2)经由第二个(或者后继)base class调用,同一个函数在virtual table中可能需要多笔对应的slots。并且在第二个或者后继base class中的虚函数表保存的是thunk代码地址。

四、 效率

通过上面第三部分的分析,多重继承和虚拟继承对象模型的较单一继承复杂的对象模型 ,造成了成员访问低效率, 表现在两个方面:对象构建时vptr的多次设定,以及this指针的调整。对于多种继承情况的效率比较如下:

情形 Vptr 设定 Data member 访问 virtual Function member 访问 效率分析
单一继承 no vptr 无 指针/引用/对象访问效率相同 直接访问 效率较高
单一继承 一次 指针/引用/对象访问效率相同 通过vptr和vtable访问 多态的引入,带来了设定vptr和间接访问虚函数等效率的降低
多重继承 多次 指针/引用/对象访问效率相同 通过vptr和vtable访问,通过第二或者后继base类指针访问需要调整this指针 除了单一继承效率降低的情形,调整this指针也带来了效率的降低
虚拟继承 多次 对象/指针/应用访问效率较低 通过vptr和vtable访问,访问虚基类需要调整this指针 除了单一继承效率降低的情形,调整this指针也带来了效率的降低

多态中的data member访问

    考察多态中几种继承情形的data member成员访问效率的关键是:members的offset位置在编译期是否能够确定。 如果访问的成员在编译期就可以确定下offset位置,不会带来额外的负担。

    理论上针对上面的继承类型,通过类对象访问,效率完全一样,因为成员在类中的位置在编

分享到:QQ空间新浪微博腾讯微博微信百度贴吧QQ好友复制网址打印

您可能想查找下面的文章:

  • C++对象内存分布详解(包括字节对齐和虚函数表)
  • 深入理解C++的对象模型
  • 深度理解c++中的this指针
  • C++中对象的常引用、动态建立和释放相关知识讲解
  • C++ 多重继承和虚拟继承对象模型、效率分析
  • C++内核对象封装单实例启动程序的类
  • C++中的常对象与常对象成员详解
  • C++中对象的常引用总结
  • C++中对象的赋值与复制操作详细解析
  • c++ 临时对象的来源

相关文章

  • 2017-05-28C语言柔性数组实例详解
  • 2017-05-28vector, list, map在遍历时删除符合条件的元素实现方法
  • 2017-05-28C++中Cbitmap,HBitmap,Bitmap区别及联系
  • 2017-05-28C语言中十六进制转十进制两种实现方法
  • 2017-05-28深入理解大数与高精度数的处理问题
  • 2017-05-28详解C++成员函数的override和final说明符的用法
  • 2017-05-28C++实现的泛型List类分享
  • 2017-05-28Java3D实例之创建空间几何模型的实现方法
  • 2017-05-28深入线性时间复杂度求数组中第K大数的方法详解
  • 2017-05-28C的|、||、&、&&、异或、~、!运算符

文章分类

  • JavaScript
  • ASP.NET
  • PHP
  • 正则表达式
  • AJAX
  • JSP
  • ASP
  • Flex
  • XML
  • 编程技巧
  • Android
  • swift
  • C#教程
  • vb
  • vb.net
  • C语言
  • Java
  • Delphi
  • 易语言
  • vc/mfc
  • 嵌入式开发
  • 游戏开发
  • ios
  • 编程问答
  • 汇编语言
  • 微信小程序
  • 数据结构
  • OpenGL
  • 架构设计
  • qt
  • 微信公众号

最近更新的内容

    • VC++中的字体设置方法详解
    • C++标准模板库函数sort的那些事儿
    • c语言中单引号和双引号的区别(顺利解决从字符串中提取IP地址的困惑)
    • c++利用stl set_difference对车辆进出区域进行判定
    • typedef和#define的用法以及区别
    • 深入解析C++编程中的纯虚函数和抽象类
    • C语言中设置用户识别码的相关函数的简单讲解
    • Effective STL 18 avoid using vector<bool>
    • 基于c++强制类型转换的(总结)详解
    • 二叉查找树的插入,删除,查找

关于我们 - 联系我们 - 免责声明 - 网站地图

©2020-2025 All Rights Reserved. linkedu.com 版权所有