当前位置: 首页 > news >正文

C++继承与多态—多重继承的那些坑该怎么填


课程总目录


文章目录

  • 一、虚基类和虚继承
  • 二、菱形继承的问题


一、虚基类和虚继承

虚基类:被虚继承的类,就称为虚基类

virtual作用:

  1. virtual修饰成员方法是虚函数
  2. 可以修饰继承方式,是虚继承,被虚继承的类就称为虚基类

注意与抽象类(有纯虚函数的类)区分开来

来看这段代码:

class A
{
public:
private:int ma;
};class B : public A
{
public:
private:int mb;
};
//A a; 4个字节
//B b; 8个字节

使用指令cl xxx.cpp -d1reportSingleClassLayoutAcl xxx.cpp -d1reportSingleClassLayoutB看一下

在这里插入图片描述 在这里插入图片描述
如果采用虚继承

class B : virtual public A

再来看一下,B从8字节变为了12字节了

在这里插入图片描述

分析:当我们遇到虚继承时候,要考虑派生类B的内存布局时,首先我们先不考虑虚继承。类B继承了基类A的ma,还有自己的mb;当我们基类A被虚继承后,基类A变为虚基类,虚基类的数据一定要被挪到派生类数据的最后面,再在最前面添加一个vbptr

在这里插入图片描述
来看一些例题

class A {};
sizeof(A)=1 //空类大小是1class B : public A {};
sizeof(B) = 1
class A
{virtual void fun() {}
};
sizeof(A)=1class B : public A {};
sizeof(B) = 4 //B的内存里有vfptr
class A
{virtual void fun() {}
};
sizeof(A)=1class B : virtual public A {};
sizeof(B) = 8 //B的内存里有vfptr和vbptr

总结:

  • vfptr:一个类有虚函数,这个类生成的对象就有vfptr,指向vftable
  • vbptr:派生类中虚继承基类,会有vbptr
  • vftable:存放RTTI指针(指向运行时RTTI信息)、虚函数地址。
  • vbtable:第一行为向上偏移量,第二行为vbptr离虚基类数据在派生类内存中的偏移量。

接下来再来看,当虚基类指针与虚函数指针在一起出现的时候会发生什么呢?

class A
{
public:virtual void func() { cout << "call A::func" << endl; }
private:int ma;
};class B : virtual public A
{
public:void func() { cout << "call B::func()" << endl; }
private:int mb;
};int main()
{// 基类指针指向派生类对象,永远指向的是派生类中基类部分数据的起始地址A* p = new B();p->func();delete p;return 0;
}

在这里插入图片描述
可以看到,调用是没有被影响到的,但是delete会出错

分析
B的内存布局:B首先从A中获取vfptrma,B中还有自己的mb

此时A被虚继承,从A中继承来的所有的东西都移动到派生类的最后面,然后在最前面补一个vbptrvbptr指向vbtablevfptr指向vftable
在这里插入图片描述

基类指针指向派生类对象,永远指向的是派生类基类部分数据的起始地址

普通情况下,派生类内存布局先是基类数据,再是派生类自己的数据,基类指针指向派生类对象时,基类指针指向的就是派生类内存的起始部分。

但是在虚继承下,基类为虚基类,虚基类的数据被挪到派生类最后面,最前面补上vbptr,此时再用基类指针指向派生类对象时候,基类指针还是指向派生类基类部分数据的起始地址,也即指向vfptr,这也是能正常调用p->func();的原因

那么在释放内存的时候呢?现在p指向的是vfptr,从vfptr开始释放内存,,而对象内存现在是从vbptr开始,这就出错了

验证一下:

class A
{
public:virtual void func() { cout << "call A::func" << endl; }void operator delete(void* p){cout << "operator delete p:" << p << endl;free(p);}
private:int ma;
};class B : virtual public A
{
public:void func() { cout << "call B::func()" << endl; }void* operator new(size_t size){void* p = malloc(size);cout << "operator new p:" << p << endl;return p;}
private:int mb;
};int main()
{// 基类指针指向派生类对象,永远指向的是派生类中基类部分数据的起始地址。A* p = new B();cout << "main p:" << p << endl;p->func();delete p;return 0;
}
operator new p:00D316A0
main p:00D316A8
call B::func()
operator delete p:00D316A8

可以看到,从A0开始new的,返回给p的是A8delete的时候也是A8,也就是从vfptr开始释放的,这是不对的

但是,这段代码也能说是错的,这和编译器有关,在Windowsvs中,是从vfptr开始释放的,但是在linuxg++下,会自动偏移到new出来的内存的起始部分来进行释放

如果在栈上开辟内存,基类指针指向派生类对象,出了作用域自己进行析构,不涉及内存的释放,这样是没有问题的,正常运行不会报错

B b;
A *p = &b;
cout << "main p:" << p << endl;
p->func();

运行结果:

main p:010FFE04
call B::func()

使用命令cl xxx.cpp -d1reportSingleClassLayoutB查看一下
在这里插入图片描述

再来看,这时有人会问了,派生类为啥不像下面这样画呢?

在这里插入图片描述

如果是这样画,也就是vfptr属于B的作用域,这是不对的,因为A中有虚函数,vfptr是从A中继承而来的

如果真的这样画的话,那就是基类中没有虚函数,从派生类中才有的虚函数

二、菱形继承的问题

多重继承:可以复用多个基类的代码到派生类中

但是多重继承中也会出现问题:菱形继承、半圆形继承等

在这里插入图片描述
这些都会导致派生类有多份间接基类的数据,此时可以采用虚继承来解决

菱形继承代码:

class A
{
public:A(int data) : ma(data) { cout << "A()" << endl; }~A() { cout << "~A()" << endl; }
protected:int ma;
};
//==========================================================
class B : public A
{
public:B(int data) : A(data), mb(data) { cout << "B()" << endl; }~B() { cout << "~B()" << endl; }
protected:int mb;
};class C : public A
{
public:C(int data) : A(data), mc(data) { cout << "C()" << endl; }~C() { cout << "~C()" << endl; }
protected:int mc;
};
//==========================================================
class D : public B, public C
{
public:D(int data) : B(data), C(data), md(data) { cout << "D()" << endl; }~D() { cout << "~D()" << endl; }
protected:int md;
};int main()
{D d(10);return 0;
}

运行结果:

A()
B()
A()
C()
D()
~D()
~C()
~A()
~B()
~A()

来看一下D的内存布局
在这里插入图片描述

用指令cl xxx.cpp -d1reportSingleClassLayoutD看看

在这里插入图片描述
可以看到调用了两次A的构造,同时数据重复了

怎么解决呢?虚继承

class A { ... };
//==========================================================
class B : virtual public A { ... };
class C : virtual public A { ... };
//==========================================================
class D : public B, public C { ... };

此时内存布局变了,解决了多份数据的问题
在这里插入图片描述

用指令cl xxx.cpp -d1reportSingleClassLayoutD看看

在这里插入图片描述
但是注意,此时编译会报错,因为现在A::ma靠在了D的作用域上面,我们要在D里面给A初始化

class D : public B, public C
{
public:D(int data) : A(data), B(data), C(data), md(data) { cout << "D()" << endl; }~D() { cout << "~D()" << endl; }
protected:int md;
};

再运行看一看结果:

A()
B()
C()
D()
~D()
~C()
~B()
~A()

多重继承的好处:可以做更多代码的复用,比如上面的例子,D继承自B和C,那么就可以B* p = new D();C* p = new D();,有两个基类,两个基类指针都可以指向派生类对象

相关文章:

C++继承与多态—多重继承的那些坑该怎么填

课程总目录 文章目录 一、虚基类和虚继承二、菱形继承的问题 一、虚基类和虚继承 虚基类&#xff1a;被虚继承的类&#xff0c;就称为虚基类 virtual作用&#xff1a; virtual修饰成员方法是虚函数可以修饰继承方式&#xff0c;是虚继承&#xff0c;被虚继承的类就称为虚基类…...

论文阅读:基于谱分析的全新早停策略

来自JMLR的一篇论文&#xff0c;https://www.jmlr.org/papers/volume24/21-1441/21-1441.pdf 这篇文章试图通过分析模型权重矩阵的频谱来解释模型&#xff0c;并在此基础上提出了一种用于早停的频谱标准。 1&#xff0c;分类难度对权重矩阵谱的影响 1.1 相关研究 在最近针对…...

1.接口测试-postman学习

目录 1.接口相关概念2.接口测试流程3.postman基本使用-创建请求&#xff08;1&#xff09;环境&#xff08;2&#xff09;新建项目集合Collections&#xff08;3&#xff09;新建collection&#xff08;4&#xff09;新建模块&#xff08;5&#xff09;构建请求请求URLheader设…...

2024年码蹄杯本科院校赛道初赛(省赛)

赛时所写题&#xff0c;简单写一下思路&#xff0c;qwq 第一题&#xff1a; 输出严格次小值&#xff0c; //#pragma GCC optimize(2)#include <iostream> #include <cstring> #include <algorithm> #include <vector> #include <queue> #incl…...

PHP蜜语翻译器在线文字转码解码源码

源码介绍 PHP蜜语翻译器在线文字转码解码源码 文字加密通话、一键转换、蜜语密码 无需数据库,可以将文字、字母、数字、代码、表情、标点符号等内容转换成新的文字形式&#xff0c;通过简单的文字以不同的排列顺序来表达不同的内容&#xff01;支持在线加密解密 有多种加密展示…...

安卓浏览器区分启动、打开、分享

搞了几个钟头&#xff0c;终于全兼容了&#xff0c;分享有2种类型&#xff01; void getDataFromIntent(Intent intent) {if (intent.getAction().equals(Intent.ACTION_VIEW)) {urln intent.getDataString();if (urln ! null) {if (urln.contains("\n"))urln url…...

C/C++ 数组负数下标

一 概述 在 C 中&#xff0c;数组是一块连续的内存空间&#xff0c;数组的下标通常用来定位这段内存中的特定元素。下标通常从 0 开始&#xff0c;最大到数组长度减 1。例如&#xff0c;一个有 10 个元素的数组&#xff0c;其有效下标范围是从 0 到 9。 当你尝试使用负数下标来…...

钓鱼网站开发原理(社会工程学)

钓鱼网站开发原理&#xff08;社会工程学&#xff09; 一、课程简介1、课程大纲2、课程目标3、知识储备 二、钓鱼网站简介1、什么是钓鱼网站2、开发&原理 三、PHP环境搭建1、简介2、自动安装MySQL/apache/PHP3、安装navicat 四、PDO表单入库案例1、语法2、显示登录表单3、入…...

如何优雅地使用 console.log 打印数组或对象

一、背景 使用 console.log 在控制台中打印数组或者对象时&#xff0c;很多时候它们的字段都是默认关闭的&#xff0c;需要手动一个个的点开&#xff0c;非常不直观且麻烦。 二、解决方案 使用 JSON.stringify() 的第三个参数 我们来看一下官方对于 JSON.stringify 的介绍 三、…...

模式分解的概念(下)-无损连接分解的与保持函数依赖分解的定义和判断、损失分解

一、无损连接分解 1、定义 2、检验一个分解是否是无损连接分解的算法 输入与输出 输入&#xff1a; 关系模式R&#xff08;U&#xff0c;F&#xff09;&#xff0c;F是最小函数依赖集 R上的一个分解 输出&#xff1a; 判断分解是否为无损连接分解 &#xff08;1&#x…...

vue3父组件获取子组件的实例对象

一&#xff0c;ref 在父组件的模板里&#xff0c;对子组件的标签定义ref属性&#xff0c;并且设置属性值&#xff0c;在方法里获取ref()获取实例对象。 父组件&#xff1a; <template><div ><div>我是父组件</div><<SonCom ref"sonComRe…...

主流框架选择:React、Angular、Vue的详细比较

目前前端小伙伴经常使用三种广泛使用的开发框架&#xff1a;React、Angular、Vue - 来设计网站 Reactjs&#xff1a;效率和多功能性而闻名 Angularjs&#xff1a;创建复杂的应用程序提供了完整的解决方案&#xff0c;紧凑且易于使用的框架 Vuejs&#xff1a;注重灵活性和可重用…...

交易者的意义是什么?

按照阿德勒的说法&#xff1a;人生的意义就是为社会创造价值&#xff0c;推动整个人类社会的发展进步。 我认同且秉持这种观点。 而在交易中&#xff0c;你是否直接或者间接为社会做贡献了呢&#xff1f;这个还真不好说。 但是做为职业交易者&#xff0c;你的存在价值&#…...

io_uring

转&#xff1a;[译] Linux 异步 I_O 框架 io_uring&#xff1a;基本原理、程序示例与性能压测&#xff08;2020&#xff09; 新一代异步IO框架 io_uring &#xff5c; 得物技术 干翻 nio &#xff0c;王炸 io_uring 来了 &#xff01;&#xff01;&#xff08;图解史上最全&a…...

构建高并发Web应用:基于Gunicorn、Flask和Docker的部署指南

目录 一 理解基础组件 什么是Flask? 什么是Gunicorn? 什么是Docker? 二 环境准备 三 构建Flask应用 创建项目结构 编写Flask应用 app/views.py 四 使用Gunicorn部署Flask应用 配置Gunicorn Gunicorn配置文件 五 使用Docker进行容器化部署 编写Dockerfile 构建…...

【Ruby简单脚本02】双色球系统

# frozen_string_literal: true require date # 生成中奖号码的工具 # 红球 1-32 篮球 1-15 def create_num nums [] 6.times do while true num rand(1..32) unless nums.include?(num) nums << num break end end end blue rand(1..15) nums…...

Netty ByteBuf 使用详解

文章目录 1.概述2. ByteBuf 分类3. 代码实例3.1 常用方法3.1.1 创建ByteBuf3.1.2 写入字节3.1.3 扩容3.1.2.1 扩容实例3.1.2.2 扩容计算新容量代码 3.1.4 读取字节3.1.5 标记回退3.1.6 slice3.1.7 duplicate3.1.8 CompositeByteBuf3.1.9 retain & release3.1.9.1 retain &a…...

怎样去掉卷子上的答案并打印

当面对试卷答案的问题时&#xff0c;一个高效而简单的方法是利用图片编辑软件中的“消除笔”功能。这种方法要求我们首先将试卷拍摄成照片&#xff0c;然后利用该功能轻松擦除答案。尽管这一方法可能需要些许时间和耐心&#xff0c;但它确实为我们提供了一个可行的解决途径。 然…...

海思SS928/SD3403开发笔记1——使用串口调试开发板

该板子使用串口可以调试&#xff0c;下面是win11 调试 该板子步骤 1、给板子接入鼠标、键盘、usb转串口 2、下载SecureCRT&#xff0c;并科学使用 下载地址&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/11dIkZVstvHQUhE8uS1YO0Q 提取码&#xff1a;vinv 3、安装c…...

JSON数据操作艺术

在现代Web开发和数据交换场景中&#xff0c;JSON&#xff08;JavaScript Object Notation&#xff09;作为一种轻量级的数据交换格式&#xff0c;扮演着至关重要的角色。它以易于阅读的文本形式存储和传输数据对象&#xff0c;而这些对象的核心便是由属性名&#xff08;键&…...

日语AI面试高效通关秘籍:专业解读与青柚面试智能助攻

在如今就业市场竞争日益激烈的背景下&#xff0c;越来越多的求职者将目光投向了日本及中日双语岗位。但是&#xff0c;一场日语面试往往让许多人感到步履维艰。你是否也曾因为面试官抛出的“刁钻问题”而心生畏惧&#xff1f;面对生疏的日语交流环境&#xff0c;即便提前恶补了…...

树莓派超全系列教程文档--(62)使用rpicam-app通过网络流式传输视频

使用rpicam-app通过网络流式传输视频 使用 rpicam-app 通过网络流式传输视频UDPTCPRTSPlibavGStreamerRTPlibcamerasrc GStreamer 元素 文章来源&#xff1a; http://raspberry.dns8844.cn/documentation 原文网址 使用 rpicam-app 通过网络流式传输视频 本节介绍来自 rpica…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互

引擎版本&#xff1a; 3.8.1 语言&#xff1a; JavaScript/TypeScript、C、Java 环境&#xff1a;Window 参考&#xff1a;Java原生反射机制 您好&#xff0c;我是鹤九日&#xff01; 回顾 在上篇文章中&#xff1a;CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...

【决胜公务员考试】求职OMG——见面课测验1

2025最新版&#xff01;&#xff01;&#xff01;6.8截至答题&#xff0c;大家注意呀&#xff01; 博主码字不易点个关注吧,祝期末顺利~~ 1.单选题(2分) 下列说法错误的是:&#xff08; B &#xff09; A.选调生属于公务员系统 B.公务员属于事业编 C.选调生有基层锻炼的要求 D…...

解决本地部署 SmolVLM2 大语言模型运行 flash-attn 报错

出现的问题 安装 flash-attn 会一直卡在 build 那一步或者运行报错 解决办法 是因为你安装的 flash-attn 版本没有对应上&#xff0c;所以报错&#xff0c;到 https://github.com/Dao-AILab/flash-attention/releases 下载对应版本&#xff0c;cu、torch、cp 的版本一定要对…...

MySQL中【正则表达式】用法

MySQL 中正则表达式通过 REGEXP 或 RLIKE 操作符实现&#xff08;两者等价&#xff09;&#xff0c;用于在 WHERE 子句中进行复杂的字符串模式匹配。以下是核心用法和示例&#xff1a; 一、基础语法 SELECT column_name FROM table_name WHERE column_name REGEXP pattern; …...

在WSL2的Ubuntu镜像中安装Docker

Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包&#xff1a; for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...

OPenCV CUDA模块图像处理-----对图像执行 均值漂移滤波(Mean Shift Filtering)函数meanShiftFiltering()

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 在 GPU 上对图像执行 均值漂移滤波&#xff08;Mean Shift Filtering&#xff09;&#xff0c;用于图像分割或平滑处理。 该函数将输入图像中的…...

Go 语言并发编程基础:无缓冲与有缓冲通道

在上一章节中&#xff0c;我们了解了 Channel 的基本用法。本章将重点分析 Go 中通道的两种类型 —— 无缓冲通道与有缓冲通道&#xff0c;它们在并发编程中各具特点和应用场景。 一、通道的基本分类 类型定义形式特点无缓冲通道make(chan T)发送和接收都必须准备好&#xff0…...

基于SpringBoot在线拍卖系统的设计和实现

摘 要 随着社会的发展&#xff0c;社会的各行各业都在利用信息化时代的优势。计算机的优势和普及使得各种信息系统的开发成为必需。 在线拍卖系统&#xff0c;主要的模块包括管理员&#xff1b;首页、个人中心、用户管理、商品类型管理、拍卖商品管理、历史竞拍管理、竞拍订单…...