C++对象模型剖析(六)一一Data语义学(三)

Data 语义学(三)

“继承” 与 Data member

上期的这个继承的模块我们还剩下一个虚拟继承(virtual inheritance)没有讲,现在我们就来看看吧。

  • 虚拟继承(Virtual Inheritance)

    虚拟继承本质就是:通过某种形式来实现共享继承,使被继承的类在继承体系中只存在一个实例。最常用的就是:解决菱形继承。

    下面我们看一个熟悉的例子:

    在这里插入图片描述

    这样我们就能够很清楚的知道多重继承和虚拟继承的区别,而且我们也能看出在多重继承的体系下,我们需要维护两个 ios base class object ,这就造成了空间和效率上的浪费,我们不仅要为这两个 ios object 分配空间,我们还要同步对他们的修改操作,来保证两个 object 是一样的。所以,解决这个问题的关键就是导入虚拟继承(virtual inheritance)。

    class ios { ... }
    class istream : public virtual ios { ... }
    class ostream : public virtual ios { ... }
    class iostream : public istream, public ostream { ... }
    

    但是在编译器中实现虚拟继承难度很高:编译器需要一个足够有效的方法,将 istream 和 ostream 各自维护的一个 ios subobject,折叠成为一个由 iostream 维护的单一的 ios subobject,并且还可以保存 base class 和 derived class 的指针(以及 引用)之间的多态指定的操作。(polymorphism assignments)。

    一般的实现方法是这样的:**Class 如果内含一个或多个 virtual base class subobjects,像 istream 那样,将被分割成两部分,一个不变区域和一个共享区域。**不变区中的数据,不管后继如何衍化,总是拥有固定的 offset,所以这一部分数据可以被直接存取。至于共享区域,所表现的就是 virtual base class object。这一部分的数据,其位置会因为每一个的派生操作而发生变化,所以它们只可以被间接存取。各家编译器实现技术之间的差异就在于间接存取的方法不同。下面就为大家介绍这三种方法。

    首先看看Vertex3d虚拟继承的层次结构。

    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 Vertetx3d : public Vertex, public Point3d {
    public:
        ...
    protected:
        float mumble;
    };
    
    // 继承关系
    //				Point2d(_x, _y)
    //				    |
    //				____|____
    //			    |        | 
    //		Vertex(next)	Point3d(_z)
    //		    	|        |
    //			    |________|
    //					|
    //				  Vertex3d(mumble)
    

    **一般的布局策略是先安排好 derived class 的不变部分,然后再建立其共享部分。**不同的编译器对 virtual inheritance 的实现的不同就体现在共享部分的实现上。

    • 第一个方法:在 derived class 种添加指向 virtual base class 的指针

      直接上书上的例子

      void Point3d::operator+=(const Point3d &rhs)
      {
          _x += rhs._x;
          _y += rhs._y;
          _z == rhs._z;
      };
      // 在这种策略下,这个运算符会被内部转换为
      _vbcPoint2d->_x += rhs._vbcPoint2d->_x;
      _vbcPoint2d->_y += rhs._vbcPoint2d->_y;
      _z += rhs._z;
      
      // 现在我们考虑另一种情况
      Point2d *p2d = pv3d;
      // 同样在这种策略下,这个转换也会被内部转换为
      Point2d *p2d = pv3d ? pv3d->_vbcPoint2d : 0;
      

      这个实现模型有两个主要的缺点:

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

      对于第二缺点,有些编译器会选择通过拷贝的操作取得所有的 nested virtual base class 指针,放到 derived class object 之中。这就解决了“固定存取时间”的问题,但是同时也付出一些空间上的代价。所以一般这些编译会提供一个选项——询问程序员是否要产生双重指针。

      看看模型的布局
      在这里插入图片描述

      对于第一个缺点,就引出了剩余的两个解决方案。

    • Microsoft 编译器引入了 virtual base class table。

      每一个class object 如果有一个或多个 virtual class table,就会由编译器安插一个指针,指向 virtual base class table。至于正真的 vitual base class pointer 将会被放在该表格中。

    • 在 virtual function table 中放置 virtual base class 的 offset(而不是地址)。

      以上面的继承体系为例,我们看看在这种策略下,每一个类(class)的布局

      image-20240302102506928

      上面的图很直观的呈现的这种将 virtual base class offset 和 virtual function table 结合的方法,virtual function table 可经由正值或负值来索引。如果是正值,很显然就是索引到了 virtual function table;如果是负值,则是索引到了 virtual base class offsets。

      // 再来看看这个 operator
      void Point3d::operator+=(const Point3d &rhs)
      {
          _x += rhs._x;
          _y += rhs._y;
          _z += rhs._z;
      }
      
      // 在这种策略下,编译器在内部做的转换如下
      void Point3d::operator+=(const Point3d &rhs)
      {
          (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;
      }
      
      // 转换操作
      Point2d *p2d = pv3d;
      Point3d *p2d = pv3d ? pv3d + pv3d->_vptr_Point3d[-1] : 0;
      

    上面的每一种方法都是一种实现模型,而不是一种标准。每一种模型都是用来解决 “存取 shared subobject 内的数据(其位置会因每次派生操作而变化)”所引发的问题。

    一般而言,virtual base class 最有效的运用形式就是:一个抽象的 virtual base class,没有任何 data member。

    也就是我们所说的抽象类,在该类中定义纯虚函数(pure virtual function),也称为接口(interface)。

    还有一小节讲的是类成员指针(data member pointer),但是有点奇怪的是实验的结果跟书上显式的不一样,这个等我弄明白了再更吧,如果你们知道为什么求求出个文章吧。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Linux操作系统项目上传Github代码仓库指南

文章目录 1 创建SSH key2.本地git的用户名和邮箱设置3.测试连接4.创建仓库5.终端项目上传 1 创建SSH key 1.登录github官网,点击个人头像,点击Settings,然后点击SSH and GPG keys,再点击New SSH key。 Title 可以随便取,但是 key 需要通过终端生成。 Linux终端执行…

Leetcode3066. 超过阈值的最少操作数 II

Every day a Leetcode 题目来源:3066. 超过阈值的最少操作数 II 解法1:模拟 两个 int 类型的数 x 和 y 做操作:min(x, y) * 2 max(x, y),得到的结果会超出 int 范围。 代码: /** lc appleetcode.cn id3066 langc…

【亲民实操课】聊聊那些在AI界立下汗马功劳的Python库,你值得拥有

嗨啰各位朋友👏,今天咱们来说说在人工智能这门深邃而又充满挑战的技术领域里,究竟有哪些Python库如同超级英雄一样,帮咱们解决实际问题,简化工作流程,从数据预处理一路狂奔到模型训练和应用落地。这次&…

Jmeter连接不同类型数据库语法

Jmeter连接不同类型数据库语法 添加:配置原件->JDBC Connection Configuration variable name for created pool:自定义一个线程池变量名database Connection Configuration database URL: 填写数据库ip、端口、dbname等,但是不同数据库…

微信小程序开发系列(十一)·小程序页面的跳转设置以及参数传递

目录 1. 跳转到商品列表 1.1 url: 当前小程序内的跳转链接 1.2 navigate:保留当前页面,跳转到应用内的某个页面。但是不能跳到 tabbar 页面 1.3 redirect: 关闭当前页面,跳转到应用内的某个页面。但不能跳转到 tabbar 页面…

在XCode中使用SwiftGen管理你的图片、配色、多语言文件等

SwiftGen是一个工具,可以为您的项目资源(如图像、本地化字符串等)自动生成Swift代码,然后你就可以像使用一个Class类一样访问你的资源了。 而且添加或更新资源后,SwiftGen也会自动更新用于访问资源的Class类。对于管理…

2023年全国职业院校技能大赛网络系统管理网络模块A模块(运维配置)

1.完成整网连通后,进入网络监控运维阶段,运维软件已安装在PC的虚拟机中,通过运维平台监控拓扑中所有网络设备(AP除外)。考试现场提供运维平台登陆的用户名密码信息。 2.通过运维平台将被监控设备纳入监控范围;通过拓扑配置功能,将网络拓扑配置到平台中。

K线实战分析系列之十八:十字线——判断行情顶部的有效信号

K线实战分析系列之十八:十字线——判断行情顶部的有效信号 一、十字线二、十字线总结三、三种特殊十字线四、长腿十字线五、墓碑十字线六、蜻蜓十字线七、特殊十字线总结 一、十字线 重要的反转信号 幅度较大的下跌,出现一根十字线,正好是在…

Unity使用UnityWebRequest读取音频长度不对的解决方法

在开发的过程中碰到这样一个问题,有的音频文件通过UnityWebRequest读取出来后,AudioClip的Length会不对,比如本身有7秒,读出来只有3秒。代码如下: IEnumerator TestEnumerator() {UnityWebRequest www UnityWebReque…

《UE5_C++多人TPS完整教程》学习笔记27 ——《P28 项目资产(Assets for The Project)》

本文为B站系列教学视频 《UE5_C多人TPS完整教程》 —— 《P28 项目资产(Assets for The Project)》 的学习笔记,该系列教学视频为 Udemy 课程 《Unreal Engine 5 C Multiplayer Shooter》 的中文字幕翻译版,UP主(也是译…

【LeetCode:98. 验证二叉搜索树 + 递归】

🚀 算法题 🚀 🌲 算法刷题专栏 | 面试必备算法 | 面试高频算法 🍀 🌲 越难的东西,越要努力坚持,因为它具有很高的价值,算法就是这样✨ 🌲 作者简介:硕风和炜,…

每周三提前预知:绝地求生PUBG七周年活动即将来袭,你最期待什么活动形式?

不知不觉PUBG正式服已经上线七个年头了,这七个年头里估计也给大家带来了很多难忘的回忆。每一年的周年活动里,代表性的衣服和枪皮那肯定是少不了。不知道大家印象最深刻的是哪一套,反正我印象最深刻的莫过于三四周年的卫衣。 这两年中三四周年…

MATLAB知识点:循环语句中的break 和 continue 关键字

​讲解视频:可以在bilibili搜索《MATLAB教程新手入门篇——数学建模清风主讲》。​ MATLAB教程新手入门篇(数学建模清风主讲,适合零基础同学观看)_哔哩哔哩_bilibili 节选自​第4章:MATLAB程序流程控制 break 和 con…

CSS的文本样式属性值,web开发难点

什么是css块元素? 块级元素是独占一行显示的。它的兄弟元素必定不会与其在同一行中(除非脱离了文档流)。通俗点来说,就是块元素(block element)一般是其他元素的容器元素 戳这里领取完整开源项目:【一线大厂前端面试题…

金融科技创新丨MogDB 数据库助四川天府银行信息化改造迈上新台阶

作为四川省重要的城市商业银行之一,四川天府银行自2001年12月成立以来,在中国银行业树立了多项标杆,逐步发展成为具有国际金融背景、跨区域、独具特色的现代精品银行。在信息系统升级改造的道路上,四川天府银行一直秉承着稳中求进…

【DIY】钱包的“电子卫士”的制作

一、工作原理 钱包的“电子卫士”电路如图1所示,其核心元件是微型蜂鸣器专用音响集成电路A,它与压电陶瓷蜂鸣片B、电池G等组成了一个体积小巧、发声响亮的简易蜂鸣器。 平时,钱包通过尼龙线与插头XP相接,而XP插入插孔XS内&#x…

android插件化开发指南,字节跳动安卓开发面试题

Android进阶延伸点 1、如何进行单元测试,如何保证App稳定 ? 参考回答: 要测试Android应用程序,通常会创建以下类型自动单元测试 本地测试:只在本地机器JVM上运行,以最小化执行时间,这种单元测…

Javaweb day11 day12

maven 1. maven的介绍 安装 idea集成maven 设置全局的 接下来和刚才一样 创建mavem项目 maven坐标 idea导入maven 依赖管理 1.依赖配置 2.依赖传递 3.依赖范围 4.生命周期

【C++】继续学习 string类 吧

开始使用 string类 吧 1 继续学习1.1 扩容机制1.2 string类对象的访问及遍历操作1.3 string类对象的修改操作1.4 其他一些成员函数 2 实践解决问题:Thanks♪(・ω・)ノ谢谢阅读!!!下一篇文章见&…

flutter弹窗输入,Android学习的三个终极问题及学习路线规划

题库非常全面包括: Android基础知识: 基本涵盖Android所有知识体系,四大组件,Fragment,WebView,事件分发,View绘制…Java基础知识&高阶知识点: 基础部分不谈了,高阶部分:泛型,反射&#xff…