10.继承与Data Member

news2024/12/25 9:11:21

目录

1、只要继承不要多态

2、加上多态

3、多重继承

4、虚拟继承


在C++继承模型中,一个derived class object所表现出来的东西,是其自己的members加上其base class(es) members的总和。至于derived class members和base class(es) members的排列顺序,则并未在C++ Standard中强制规定,理论上编译器可以自己安排。在大部分的编译器上,base class members 总是先出现,但是virtual base class除外(一般而言,任何一条通则一旦碰上virtual base class 就没辙了,这里也不例外)。

对于如下的代码,他们的数据布局是这个样子的。

//supporting abstract data type
class Point2d{
public:
	//constructor(s)
	//operations
	//access functions
private:
	float x,y;
};
class Point3d{
public:
	//constructor(s)
	//operations
	//access functions
private:
	float x,y,z;
};

1、只要继承不要多态

或许程序员希望,不论是2D或者3D坐标点,都能够共享同一个实例,但有希望能够继续使用“与类型性质相关”的实例。我们设计一个策略,就是从Point2d中派生出Point3d,于是Point3d将继承x和y的数据和方法。带来的影响则是可以共享“数据本身”以及“数据处理方法”,并将之局部化。

class Point2d{
public:
	Point2d(float x=0.0, float y=0.0)
		:_x(x),_y(y){};
	float x(){return _x;}
	float y(){return _y;}
	
	void x(float newX){_x = newX;}
	void y(float newY){_y = newY;}
	void operator+=(const Point2d & rhs)
	{
		_x += rhs.x();
		_y += rhs.y();
	}
	//... more members
protected:
	float _x, _y;
};
//inheritance from concrete class
class Point3d : public Point2d{
public:
	Point3d(float x=0.0, float y=0.0,float z=0.0):
		:Point2d(x,y),_z(z){};
	float z(){return _z;}
	void z(float newZ){_z = newZ;}
	
	void operator+=(const Point3d& rhs)
	{
		Point2d::operator+=(rhs);
		_z += rhs.z();
	}
	//... more members
protected:
	float _z;
};

我们看看上面的代码,这个实现明显的表现出抽象类之间的紧密关系。当这两个class独立的时候,Point2d object和Point3d object的声明和使用都不会有所改变。下图显示了Point2d和Point3d继承关系的实物布局,其间并没有声明virtual接口。

把原本独立的class凑成一对“type/subtype”,并带有继承关系,此时容易犯什么错误呢?经验不足的人可能重复设计一些相互操作的函数。以我们例子中的constructor和operator+=为例。

第二个容易犯的错误是,把一个class分解成两层或者更多层,有可能会为了“表现class 体系之抽象化”而膨胀所需的空间。C++语言保证“出现derived class 中的base class subobject有其完整原样性”,正是重点所在。我们从一个具体的class开始。

class Concrete
{
public:
	//...
private:
	int val;
	char c1;
	char c2;
	char c3;
};

在一台32位的机器中,每一个Concrete class object的大小都是8 bytes,具体表现如下:

现在我们决定把Concrete分裂成三层结构。

class Concrete1
{
public:
	//...
private:
	int val;
	char bit1;
};
class Concrete2 : public Concrete1
{
public:
	//...
private:
	char bit2;
};
class Concrete3 : public Concrete2
{
public:
	//...
private:
	char bit3;
};

那现在Concrete3 object的大小是16bytes,比原来的设计多了100%,这是由于“出现derived class 中的base class subobject有其完整原样性”。用下图进行解释。

然而,为什么要保证“出现在derived class中的base class subobject有其完整的原样性”吗?

当编译器不保证“出现在derived class中的base class subobject有其完整的原样性”,那么就会出现以下的布局

下面的代码中存在一个BUG,这个BUG可以用图解释,只不过这个BUG需要非常长的时间找到。

Concrete2 *pc2;
Concrete1 *pc1_1, *pc1_2;
pc1_1 = pc2;
//这里有个BUG:derived class subobject被覆盖掉了,于是其bit2 member现在有了一个非预期的数值
*pc1_1 = *pc1_2;

2、加上多态

如果我要处理一个坐标点,而不打算在乎它是一个Point2d或者Pointe3d实例,那么我需要在继承关系中提供一个virtual function接口。让我们看看如果这么做,情况会有什么改变。

class Point2d
{
public:
	Point2d(float x=0.0, float y=0.0)
		:_x(x), _y(y){};
	//x和y的存取函数与前一版相同
	//由于对不同维度的点,这些函数操作固定不变,所以不必设计为virtual
	
	//加上z的保留空间(目前什么也没做)
	virtual float z(){ return 0.0; }//译注:2d的点z为0.0是合理的
	virtual void z(float){}
	
	//设定以下的运算符是virtual
	virtual void
	operator+=(const Point2d&rhs)
	{
		_x += rhx.x();
		_y += rhx.y();
	}
	//...more members
	
protected:
	float _x,_y;
};

只有当我们企图以多态的方式(polymorphically)处理2d或3d坐标点时,在设计之中导入一个virtual接口才合理。也就是说,写下如下的代码,其中p1和p2可能是2d,也可能是3d坐标点。

void foo(Point2d &p1, Point2d &p2)
{
	p1 += p2;
	//...
}

这并不是之前的任何设计支持的,这种弹性正是面向对象设计的重要部分。为了支持这种弹性,势必会对我们的Point2d class带来空间和存取时间上的额外负担:

  • 导入一个和Point2d有关的virtual table,用来存放它所声明的每一个virtual functions的地址。这个table的元素个数一般而言是被声明的virtual functions的个数,再加上一个或者两个slots(用以支持runtime type identification)。
  • 在每一个class object 中导入一个vptr,提供执行期的链接,使得每个object能够找到相应的virtual table。
  • 加强constructor,使它能够为vptr设定初值,让它指向class所对应的virtual table。这可能意味着在derived class和每一个base class的constructor中,重新设定vptr值。其情况视编译器优化的积极性而定。
  • 加强destructor,使它能够抹除“指向class之相关的virtual table”的vptr,要知道vptr很可能已经在derived class destructor中被设定为derived class的virtual table地址。

目前,C++编译器领域有一个主要的讨论主题:把vptr放置在class object的哪里会更好?最初在cfront中,它被放在了class object的尾部,用以暴漏base class C struct的对象布局。

但是到了C++2.0,开始支持虚拟继承和抽象基类,并且由于面向对象的范式(OO paradigm)的兴起,某些编译器开始把vptr放在class object的起头处。如图所示:

把vptr放在class object的前端,对于“在多重继承下,通过指向class members的指针调用virtual function”,会带来一些帮助。否则,不仅“从class object起始点开始量起”的offset必须执行期间备妥,甚至与class vptr之间的offset也必须备妥。当然,vptr放在前端,代价是丧失C预言兼容性。这种丧失有多少意义?有多少程序能从一个C struct派生一个具有多态性质的class呢?

3、多重继承

过于复杂且不实用,不考虑。

4、虚拟继承

多重继承的一个语意上的副作用是,它必须支持某种形式的“shared subobject继承”。典型的一个例子是最早的iostream library:

//pre-standard iostream implementation
class ios {...};
class istream : public ios {...};
class ostream : public ios {...};
class iostream :
	public istream, public ostream {...};

下图表现的是iostream的继承关系。左边是多重继承,右边是虚拟继承。

不论是istream还是ostream都含有一个ios subobject。然而在iostream的对象布局中,我们只需要单一一份ios subobject就好。语言层面的解决办法是导入所谓的虚拟继承。

//pre-standard iostream implementation
class ios {...};
class istream : public virtual ios {...};
class ostream : public virtual ios {...};
class iostream :
	public istream, public ostream {...};

正如其语意所呈现的复杂度,要在编译器中支持虚拟继承,实在难度颇高。在上述的iostream中,实现技术的难度在于,要找到一个足够有效的方式,将istream和iostream各自维护的ios subobject,折叠成一个由iostream维护的单一ios subobject,并且还可以保存base class和derived class的指针(以及reference)之间的多态指定操作(polymorphism assignments)。

一般的实现方法如下所述。Class如果内含一个或者多个virtual base class subobject,像istream那个样子,将被分割成两部分:一个不变区域和一个共享区域不变区域的数据,不管后继如何衍化,总是拥有固定的offset(从object的开头算起),所以这一部分数据可以被直接存取。至于共享区域,所表现的就是virtual base class subobject。这一部分的数据,其位置会因为每次派生操作而变化,所以他们只能被间接存取。以下面的例子进行说明。

class Point2d{
public:
	...
protected:
	float _x,_y;
};
class Vertex : public virtual Point2d{
public:
	...
protected:
	Vertex *next;
};
class Point3d : public virtual Point2d{
public:
	...
protected:
	float _z;
};
class Vertex3d :
	public Vertex, public Point3d
{
public:
	...
protected:
	float mumble;
};

下图可以表示上图的关系:

如果我们有以下的Point3d运算符,那么内部的转换方式可能是这样子的。

void Point3d::operator+=(const Point3d &rhs)
{
	_x += rhs.x;
	_y += rhs.y;
	_y += rhs.y;
};
在cfront策略下,这个运算符会被内部转换成:
//虚拟的C++ 代码
__vbcPoint2d->_x += rhs.__vbcPoint2d->_x; //译注:vbc是virtual base class
__vbcPoint2d->_y += rhs.__vbcPoint2d->_y;
_z += rhs._z;

而一个derived class和一个base class的实例相互转换,像这样子:

Point2d *p2d = pv3d;
在cfront实现模型中,下面是虚拟的C++代码
Point2d *p2d = pv3d ? pv3d->__vbcPoint2d : 0;

更加详细的描述见下面的图,cfront实现方式。

上图实现的方式存在两个主要的缺点:

  • 每一个对象必须针对其每一个virtual base class 背负一个额外的指针。然而,理想的情况是我们希望class object 有固定的负担,不会因为其virtual base classes的个数而有所变化。这如何解决?
  •  由于虚拟集成串链的加长,导致间接存取层次的增加。如果我有三层虚拟派生,我就需要三次间接存取(经过三个virtual base class指针)。然而理想上我们却希望有固定的存取时间,不会应为虚拟派生的深度而改变。

第一个问题,Bjarne比较喜欢的方法是在virtual function table 中放置virtual base class的offset(而不是地址)。下图中显示了这种base class offset实现模型。我在Foundation项目中实现出该方法,将virtual base class offset 和virtual function entries混杂在一起。在新近的Sun编译器中,virtual function table可经由正值或者负值来索引。如果是正值,很显然就索引到virtual functions;如果是负值,则索引到virtual base class offsets。在这样的策略下,Point3d的operator+=运算符必须被转换成以下的形式:

//虚拟C++代码
(this + __vptr__Point3d[-1])->_x +=
	(&rhs + rhs.__vptr__Point3d[-1])_x;
(this + __vptr__Point3d[-1])->_y +=
	(&rhs + rhs.__vptr__Point3d[-1])_y;
_z += rhs.z;

Derived class实例和base class 实例之间的转换操作,像这样子:

Point2d *p2d = pv3d;
在cfront实现模型中,下面是虚拟的C++代码
Point2d *p2d = pv3d ? pv3d + pv3d->__vptr__Point3d[-1] : 0;

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2212650.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

基坑气膜:为清洁施工提供强力保障—轻空间

随着城市建设的不断推进,环保要求也日益提高。基坑气膜作为一种新型的施工技术,不仅在防尘降噪方面表现出色,还能支持复杂的施工设备运行,真正实现了从源头解决扬尘和噪音问题。 高效防尘,优化施工环境 传统施工中&…

Hyper-V管理器连接到服务器出错。请检查虚拟机管理服务是否正在运行以及是否授权你连接到此服务器。

尝试连接到服务器”XXXXXX"时出错。请检查虚拟机管理服务是否正在运行以及是否授权你连接到此服务器。 计算机"XXXXXX"上的操作失败: WinRM客户端无法处理该请求。如果身份验证方案与Kerberos不同,或者客户端计算机未加入到域中,则必须使…

工业物联网一直是风口,可视化大屏就是门面

工业物联网作为当下的热门领域,一直处于风口浪尖。而在这个领域中,可视化大屏确实充当着重要的门面角色。 可视化大屏以其震撼的视觉效果和直观的数据展示,为工业物联网赋予了强大的表现力。当人们走进工业物联网的应用场景,首先映…

Jina ColBERT v2: 一个多语言的晚期交互信息检索模型

Jina AI发布 Jina ColBERT v2版本, Jina ColBERT v2是一个多语言的晚期交互(Late Interaction)信息检索模型,基于BERT架构开发,旨在优化查询和文档之间的匹配和排序。用于在搜索引擎、推荐系统、问答系统等应用中实现高…

20 个精选 Midjourney SREFs 及额外代码,等你来用!

今天我整理了 Midjourney 的参考 ID 801 到 850,并选择了 20 种色彩和艺术风格多样的样式。灵感来源于 _旧金山的彩绘女士_,这是加利福尼亚州著名的排屋系列。 我使用文本提示“城市中的一组排屋”生成了图像,并应用了 20 个选定的 SREF。每张…

【Java】I/O 操作详解

📃个人主页:island1314 ⛺️ 欢迎关注:👍点赞 👂🏽留言 😍收藏 💞 💞 💞 目录 1. 引言 🚀 2. File 类 📕 2.1 创建 File 对象 …

讲个故事(升级版)1.0

一、总述 离线强化学习(Offline RL)是交互推荐中的一种常用技术,它离线地学习离线数据以学习策略而不必与用户进行在线交互。Offline RL存在高估用户对离线数据中很少出现的物品的偏好的问题,当前采用了保守主义去解决这一问题&a…

基于SpringBoot的流浪动物救助系统

作者:计算机学姐 开发技术:SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等,“文末源码”。 专栏推荐:前后端分离项目源码、SpringBoot项目源码、Vue项目源码、SSM项目源码、微信小程序源码 精品专栏:…

新160个crackme -081-fireworx.9

运行分析 输入key,点击OK无反应 PE分析 Delphi程序,32位,无壳 静态分析&动态调试 ida搜索关键字符串,双击进入函数 通过静态分析,发现程序逻辑:1、获得固定值"678234",转换为int2…

网络学习第二篇

认识网关和路由器 这里大家先了解一下什么三层设备。 三层设备 三层设备是指在网络架构中能够工作在第三层(网络层)的设备,通常包括三层交换机和路由器。这些设备可以根据IP地址进行数据包的转发和路由选择,从而在不同的网络之间…

【Solr】Solr搜索引擎下载、安装、使用及跟Elasticsearch的对比(保姆篇)

文章目录 Solr简单介绍Solr 版本与 JDK版本 兼容情况安装与配置(Windows)安装与配置(Linux)应用案例(电商产品搜索系统)为什么要用Solr,不用数据库的模糊查询Solr对比Elasticsearch 更多相关内容可查看 Solr官方文档&a…

JAVA智能代驾跑腿系统一站式服务系统源码小程序

​探索“智能代驾跑腿系统”的便捷魅力 🚗 一、智能代驾:安全出行的首选 在这个快节奏的城市生活中,偶尔的聚会小酌或深夜加班后,如何安全回家成了不少人心中的小困扰。幸运的是,“智能代驾跑腿系统”应运而生&#x…

RJ45网线T568B接法

目录 1.说明 2.应用 3.方法 4.网络制作注意 1.说明 常规的网线T568B和T568A,为了保持最佳的兼容性,普遍采用T568B标准来制作。 2.应用 T568B:主要应用于平行线(即直连线)和交叉线(用于两台设备之间的直接连接,如两台计算机互连或计算机…

应用商店上新:MainConcept Transcoder和Live Streaming Software App

在Akamai云计算平台上运行工作负载的你也许还不知道,为了帮助用户更容易地找到并快速部署各类解决方案,Akamai提供了一个丰富的应用商店(Marketplace),其中包含各类经过验证,可以在Akamai云计算平台上轻松部…

自由学习记录(7)

文件的判断是否存在,带上文件自己的名字 XmlSerializer (Person)serializer.Deserialize(reader); 如果出错之后,没有try来接,就会直接程序报错暂停, 有了的话无论如何都会继续正常进final using则是正常 为什么要用 using&a…

DICOM是什么?如何成为医学成像和通讯的国际标准的?

DICOM DICOM是医学数字成像和通信标准(Digital Imaging and Communications in Medicine),它是一种用于描述医学图像及其元数据如何存储和在设备间传输的标准。 DICOM标准支持多种医学成像模态的数据存储,包括CT、PET、MRI、X射线…

Golang | Leetcode Golang题解之第478题在圆内随机生成点

题目: 题解: type Solution struct {radius, xCenter, yCenter float64 }func Constructor(radius, xCenter, yCenter float64) Solution {return Solution{radius, xCenter, yCenter} }func (s *Solution) RandPoint() []float64 {r : math.Sqrt(rand.…

【视觉分割新SOTA|论文解读4】一种最先进的图像分割模型SAM——Zero-Shot Transfer ExperimentsDiscussion

【视觉分割新SOTA|论文解读4】一种最先进的图像分割模型——Segment Anything Model (SAM)——Zero-Shot Transfer Experiments&Discussi 【视觉分割新SOTA|论文解读4】一种最先进的图像分割模型——Segment Anything Model (SAM)——Zero-Shot Transfer Experiments&…

iOS 18.0.1 修復 iPhone 16 觸控失靈、訊息過早錄音等問題

上月末不少 iPhone 16、16 Pro 用戶表示自己的螢幕出現了觸摸後突然大面積無法響應的情況,當時我們猜測 Apple 會推出相應的修復更新,如今為解決這個問題而來的 iOS 18.0.1 終於正式上線了。不過在更新日誌中,官方並未說明導致斷觸的具體原因…

【企业办公系统】签到及考勤数据管理

员工在系统点击签到时,系统会从是否工作日、是否请假、签到时间和地点是否正确上进行判断,确定是否计入考勤。其中,考勤状态分为正常、地区异常、早退异常、迟到异常、旷工异常。此外,除了通过逻辑判断以外,系统还需要…