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

C++惯用法之空基类优化

相关系列文章

C++惯用法之Pimpl

C++惯用法之CRTP(奇异递归模板模式)

C++之std::tuple(二) : 揭秘底层实现原理

目录

1.空类

2.空基类优化

3.内存布局原则

4.实例分析

5.总结


1.空类

        C++ 中每个对象的实例都可以通过取地址运算符获取其在内存布局中的开始位置,因此每个类对象至少需要占用一个字节的空间。空类是指不包含非静态数据成员的类,但是可以包含成员函数及静态成员。C++ 中空类的大小是 1 字节。

class CEmpty1
{};
class CEmpty2
{static int i;
};class CEmpty3
{
public:void func1() {};void func2() {};
};int main()
{cout << "CEmpty1大小:" << sizeof(CEmpty1) << endl; //输出: 1cout << "CEmpty2大小:" << sizeof(CEmpty2) << endl; //输出: 1cout << "CEmpty3大小:" << sizeof(CEmpty3) << endl; //输出: 1return 0;
}

结果是1,它是空的怎么不是0呢?

因为空类同样可以被实例化,每个实例在内存中都有一个独一无二的地址,为了达到这个目的,编译器往往会给一个空类隐含的加一个字节,这样空类在实例化后在内存得到了独一无二的地址.所以上述大小为1。

2.空基类优化

注:空基类优化可简称为EBO (empty base optimization)或者 EBCO (empty base class optimization)

在没有歧义的情况下,C++ 允许空基类的子对象大小为 0。

一般来讲,对一个既有类进行扩展时,除非有更好的理由采用继承(有虚函数需要重新实现、有受保护的私有成员需要访问,否则采用组合的方式进行扩展。

现对比一下两种模式,第一种,类中把空类做为成员变量使用,然后通过这个来获得被包含类的功能,如:

class CEmpty {};
class CDerived1 {CEmpty m_base;int m_i;//other function...
};

另一种直接采用继承的方式来获得基类的成员函数及其他功能等等。如:

class CDerived2 : public CEmpty {int m_i;//other function...
};

接下来做个测试:

std::cout<<sizeof(CDerived1)<<std::endl; //输出: 8
std::cout<<sizeof(CDerived2)<<std::endl; //输出:4

第一种,本来只占1字节,会因为字节对齐,进行扩充到4的倍数,最后就是8字节。
对比这两个发现,第二种通过继承方式来获得基类的功能,并没有产生额外大小的优化称之为EBO(空基类优化)。

3.内存布局原则

        C++的设计者不允许类的大小为0,其原因有很多,比如由它们构成的数组,其大小必然也是0,这会导致指针运算中普遍使用的性质失效。比如,假设类型ZeroSizedT的大小为0,则下面的操作会出现错误:

ZeroSizedT  z[10];
auto v =  &z[9]  - &z[2];  // 计算指针/地址之间的距离

        正常情况下,上例中的差值是通过将两个地址之间的字节数除以指针指向的类型的大小得出来的,但是它们的大小是0时,该关系就显然就不成立了。

        尽管C++中没有大小为0的类型,但是C++规定,当空类作为基类时,不需要为其分配空间,前提是这样做不会导致它被分配到与其他对象或者同类型的子对象相同的地址上。看个例子:

#include <iostream>
class EmptyClass{using Bool = bool; //类型别名成员不会让一个类成为非空类
};
class EmptyFoo : public EmptyClass{
};
class EmptyThree : public EmptyFoo{
};
int main(){std::cout << sizeof(EmptyClass) << std::endl; //输出:1std::cout << sizeof(EmptyFoo) << std::endl; //输出:1std::cout << sizeof(EmptyThree ) << std::endl; //输出:1
}

如果编译器支持空基类优化,上面程序的所有输出结果相同,但是均不为0。也就是说,在类EmptyFoo 中的类 EmptyClass没有分配空间 。 如下图:b3dc5c1c04f54e1b917e3b7ee74f8c0c.png

如果不支持空基类优化,上面程序的输出结果不同。布局如下图:460774284a654c6395d2441d2d246b11.png

再看个例子:

#include <iostream>
class EmptyClass{using Bool = bool;  //类型别名成员不会让一个类成为非空类
};
class EmptyFoo : public EmptyClass{
};
class NoEmpty :public EmptyClass,  public EmptyFoo{
};
int main(){std::cout << sizeof(EmptyClass) << std::endl; //输出:1std::cout << sizeof(EmptyFoo) << std::endl; //输出:1std::cout << sizeof(NoEmpty) << std::endl; //输出:2
}

        NoEmpty 为什么不为空类呢?这是因为NoEmpty 的基类EmptyClass和EmptyFoo 不能分配到同一地址空间,否则EmptyFoo 的基类EmptyClass和NoEmpty 的EmptyClass会撞到同一地址空间上。换句话说,两个相同类型的子对象偏移量相同,这是C++布局规则不允许的

b8754183586d4bd7bcd6955677a1db2d.png

        对空基类优化进行限制的根据原因在于:我们需要能比较两个指针是否指向同一对象。由于指针几乎总是用地址内部表示,所以我们必须保证两个不同的地址(即两个不同的指针)对应两个不同的对象。

        这个限制也许看起来不是非常重要。然而,在实践中经常会遇到相关问题,因为许多类往往继承自某些空类的一个小集合,而这些空类又往往定义了一些共同的类型别名。当这样的类的两个子对象被用在同一个完整对象中时,优化就会被阻止。

        就算有此限制,EBCO仍是模板库的一个重要优化,因为有些技巧要依赖于某些基类的引入,而引入这些基类只是为了引入新的类型别名或者在不增加新数据的情况提供额外功能。

4.实例分析

std::tuple实际也应用了空基类优化,如:

struct Base1 {}; // 空类
struct Base2 {}; // 空类
struct Base3 {}; // 空类int main()
{std::cout << sizeof(std::tuple<Base1, Base2, Base3>) << "," << sizeof(std::tuple<Base1, Base2, Base3, int>);
}
// 输出为1,4

本节介绍std::tuple中如何应用EBO,本文以mingw平台上的实现为例进行讲解。

tuple的模板参数可以支持接收任意类型,熟悉可变模板参数的同学可以快速实现如下代码:

template<typename ...Args>
struct Tuple;
template<>
struct Tuple<> {
};template<typename Head, typename ...Tail>
struct Tuple<Head, Tail...> {Head h;Tuple<Tail...> t;
};

此时模板参数类型为空类时存在内存浪费;下一步应用EBO优化得到:

template<typename ...Args>
struct Tuple;
template<>
struct Tuple<> {
};template<typename Head, typename ...Tail>
struct Tuple<Head, Tail...> : private Head, Tuple<Tail...> {
};

但Head可能为int或者final类等不可继承类型,因此引入TupleEle:

template<typename T, bool = std::is_class<T>::value && !std::is_final<T>::value>
struct TupleEle;template<typename T>
struct TupleEle <T, false> {T value;T& Get() { return value; }
};template<typename T>
struct TupleEle <T, true> : private T {T& Get() { return *this; }
};template<typename ...Args>
struct Tuple;template<>
struct Tuple<> {
};template<typename Head, typename ...Tail>
struct Tuple<Head, Tail...>: private TupleEle<Head>, private Tuple<Tail...> {
};

此时如果送入重复类型,则重复继承了TupleEle<xxx>,导致 派生类转换到基类存在歧义,因此进一步修改为:

template<size_t index, typename T, bool = std::is_class<T>::value && !std::is_final<T>::value>
struct TupleEle;template<size_t index, typename T>
struct TupleEle <index, T, false> {T value;T& Get() { return value; }
};template<size_t index, typename T>
struct TupleEle <index, T, true> : private T {T& Get() { return *this; }
};template<typename ...Args>
struct Tuple;
template<>
struct Tuple<> {
};template<typename Head, typename ...Tail>
struct Tuple<Head, Tail...>: private TupleEle<sizeof...(Tail), Head>, private Tuple<Tail...> {
};

得益于EBO继承关系,在实现Get<xxx>(tuple)利用模板参数推导,可以在常量时间内获取对应元素,补充Get之后的完整代码如下:

template<size_t index, typename T, bool = std::is_class<T>::value && !std::is_final<T>::value>
struct TupleEle;template<size_t index, typename T>
struct TupleEle <index, T, false> {template<typename U>TupleEle(U&& u) : value(std::forward<U>(u)) {};T& Get() { return value; }
private:T value;
};template<size_t index, typename T>
struct TupleEle <index, T, true> : private T {template<typename U>TupleEle(U&& u) : T(std::forward<U>(u)) {};T& Get() { return *this; }
};template<typename ...Args>
struct Tuple;
template<>
struct Tuple<> {
};template<typename Head, typename ...Tail>
struct Tuple<Head, Tail...>: TupleEle<sizeof...(Tail), Head>, private Tuple<Tail...> {template<typename H, typename ...Rest>Tuple(H&& h, Rest&&...rest) : TupleEle<sizeof...(Tail), Head>(std::forward<H>(h)),Tuple<Tail...>(std::forward<Rest>(rest)...){}template<size_t index, typename ...Ts>friend decltype(auto) Get(Tuple<Ts...>& t);
};template<size_t index, typename T>
T& GetIndex(TupleEle<index, T>& te) { return te.Get(); }template<size_t index, typename ...Ts>
decltype(auto) Get(Tuple<Ts...>& t) { return GetIndex<sizeof...(Ts) - index -1>(t); }

在GetIndex调用时通过模板参数推导,index确定,推导出对应T;

std::tuple在vs2019平台上的实现跟mingw上的实现还是有些差异,具体的差异可以查看我的另外一篇博客:

C++之std::tuple(二) : 揭秘底层实现原理-CSDN博客

5.总结

        为了减少空基类对象的内存占用,C++编译器引入了空基类优化。当一个类作为基类被继承时,如果这个基类是空的,编译器会将派生类对象的地址指向基类对象的地址,从而实现对基类对象的共享。这样一来,派生类对象就可以共享基类对象的内存空间,避免了额外的内存开销。

        空基类优化可以提高程序的性能和内存利用率,特别是在涉及大量继承关系和多重继承的情况下。通过减少空基类对象的内存占用,可以降低内存开销,并提高程序的运行效率。

        需要注意的是,不是所有的编译器都支持空基类优化技术。因此,在使用该技术时,需要检
查目标编译器是否支持该优化,并确保代码符合优化的要求。

参考:空基类优化 - cppreference.com

相关文章:

C++惯用法之空基类优化

相关系列文章 C惯用法之Pimpl C惯用法之CRTP(奇异递归模板模式) C之std::tuple(二) : 揭秘底层实现原理 目录 1.空类 2.空基类优化 3.内存布局原则 4.实例分析 5.总结 1.空类 C 中每个对象的实例都可以通过取地址运算符获取其在内存布局中的开始位置&#xff0c;因此每个类…...

【生成式AI】ChatGPT 原理解析(2/3)- 预训练 Pre-train

Hung-yi Lee 课件整理 预训练得到的模型我们叫自监督学习模型&#xff08;Self-supervised Learning&#xff09;&#xff0c;也叫基石模型&#xff08;foundation modle&#xff09;。 文章目录 机器是怎么学习的ChatGPT里面的监督学习GPT-2GPT-3和GPT-3.5GPTChatGPT支持多语言…...

Day03:Web架构OSS存储负载均衡CDN加速反向代理WAF防护

目录 WAF CDN OSS 反向代理 负载均衡 思维导图 章节知识点&#xff1a; 应用架构&#xff1a;Web/APP/云应用/三方服务/负载均衡等 安全产品&#xff1a;CDN/WAF/IDS/IPS/蜜罐/防火墙/杀毒等 渗透命令&#xff1a;文件上传下载/端口服务/Shell反弹等 抓包技术&#xff1a…...

C++多线程同步(上)

多线程同步 引言总述详情互斥锁示例运行结果分析条件变量示例一实现分析优化运行结果示例二实现代码运行结果示例三实现代码运行结果读写锁示例实现代码注意分析运行结果附言实现运行结果运行结果个人心得引言 项目中使用多线程,会遇到两种问题,一种是对共享资源的访问时需要…...

猜猜心里数字(个人学习笔记黑马学习)

1.定义一个变量&#xff0c;数字类型&#xff0c;内容随意 2.基于input语句输入猜想的数字&#xff0c;通过if和多次elif的组合&#xff0c;判断猜想数字是否和心里数字一致 num5if int(input("请输入第一次猜想的数字&#xff1a;"))5:print("猜对了&#xff0…...

实用Pycharm插件

Pycharm的离线安装&#xff1a;https://plugins.jetbrains.com/ 需要根据对应的Pycharm/Goland版本选取所需的 对于实用的插件如下&#xff1a; 实时查看每一行的git blame信息&#xff1a; Gittoolbox 转换IDE的英文为中文&#xff1a;Chinese IDE侧格式化json字符串&#…...

数据结构试题练习

(1). 假如队列未满&#xff0c;现有变量data需要入队,请写出表达式; if( (tail1)%SEQLEN ! head ) {seqn[tail] data;tail (tail1)%SEQLEN; } (2). 假如队列未空&#xff0c;现在需要从队列取一个元素并赋值给变量data&#xff0c;请写出表达式; if( head ! tail ) {data se…...

s-table和columns初始化不完整,造成table文件的filter报错

问题 顺藤摸瓜找errorHandler.js文件 发现文件并没有什么问题 顺藤摸瓜找index.vue文件 首先找到报错的filter&#xff0c;发现与columnsSetting相关 找到columnsSetting发现等于columns 返回自己使用S-table组件的地方&#xff0c;发现columns初始化时仅初始化为ref()未表明…...

SLA 是什么?如何实现 SLA 管理

随着业务的不断壮大&#xff0c;为了满足日益增长的客户需求&#xff0c;网络必须保持与这些需求同步。同时&#xff0c;为了提高最终用户的体验&#xff0c;运维人员/网络管理员在监控企业级网络时遇到了不少瓶颈&#xff0c;必须不断审查网络&#xff0c;以确保提供的服务质量…...

火灾安全护航:火灾监测报警摄像机助力建筑安全

火灾是建筑安全中最常见也最具破坏力的灾难之一&#xff0c;为了及时发现火灾、减少火灾造成的损失&#xff0c;火灾监测报警摄像机应运而生&#xff0c;成为建筑防火安全的重要技术装备。 火灾监测报警摄像机采用高清晰度摄像头和智能识别系统&#xff0c;能够全天候监测建筑内…...

JavaScript 基础学习笔记(五):函数、作用域、匿名函数

目录 一、函数 1.1 声明和调用 1.2 形参和实参 1.3 返回值 二、作用域 2.1 全局作用域 2.2 局部作用域 三、匿名函数 3.1 函数表达式 3.2 立即执行函数 一、函数 理解函数的封装特性&#xff0c;掌握函数的语法规则 1.1 声明和调用 函数可以把具有相同或相似逻辑的代…...

Qt环境配置VTK

Qt与VTK的结合为开发者提供了强大的跨平台图形界面开发能力和三维可视化处理能力。本教程旨在详细介绍如何配置Qt环境以使用VTK库&#xff0c;从而为开发者打造高效、强大的三维可视化应用。 一、准备工作 在开始之前&#xff0c;确保您的开发环境中已经安装了Qt和CMake。Qt提…...

腾讯云最新活动_腾讯云促销优惠_代金券-腾讯云官网入口

腾讯云服务器多少钱一年&#xff1f;62元一年起&#xff0c;2核2G3M配置&#xff0c;腾讯云2核4G5M轻量应用服务器218元一年、756元3年&#xff0c;4核16G12M服务器32元1个月、312元一年&#xff0c;8核32G22M服务器115元1个月、345元3个月&#xff0c;腾讯云服务器网txyfwq.co…...

如何创建自己的Spring Boot Starter并为其编写单元测试

当我们想要封装一些自定义功能给别人使用的时候&#xff0c;创建Spring Boot Starter的形式是最好的实现方式。如果您还不会构建自己的Spring Boot Starter的话&#xff0c;本文将带你一起创建一个自己的Spring Boot Starter。 快速入门 创建一个新的 Maven 项目。第三方封装的…...

数据分析---常见处理逻辑

目录 数据清洗数据转换数据聚合数据筛选增删改查(以查为例)数据清洗 去除重复值:使用DISTINCT关键字去除重复行。//这将返回一个包含所有不重复城市的结果集 SELECT DISTINCT city FROM students;处理缺失值:使用IS NULL或IS NOT NULL判断是否为空值,并使用COALESCE或CASE…...

2024-02-26(金融AI行业概览与大数据生态圈)

1.最开始的风控是怎么做的&#xff1f; 人审 吃业务经验 不能大批量处理&#xff0c;效率低下 不适用于移动互联网的金融场景 2.建模的概念 建模就是构造一个数学公式&#xff0c;能将我们手上有的数据输入进去&#xff0c;通过计算得到一些预测结果。 比如初高中学习的…...

git忽略某些文件(夹)更改说明

概述 在项目中,常有需要忽略的文件、文件夹提交到代码仓库中,在此做个笔录。 一、在项目根目录内新建文本文件,并重命名为.gitignore,该文件语法如下 # 以#开始的行,被视为注释. # 忽略掉所有文件名是 a.txt的文件. a.txt # 忽略所有生成的 java文件, *.java # a.j…...

python爬虫实战:获取电子邮件和联系人信息

引言 在数字时代&#xff0c;电子邮件和联系人信息成为了许多企业和个人重要的资源&#xff0c;在本文中&#xff0c;我们将探讨如何使用Python爬虫从网页中提取电子邮件和联系人信息&#xff0c;并附上示例代码。 目录 引言 二、准备工作 你可以使用以下命令来安装这些库&a…...

post请求同时上传文件并传递其他参数的前后端写法

最近有一需求&#xff0c;post请求从前端上传一个文件同时传递一个参数&#xff0c;多次实验后记录下两种写法&#xff1a; 方法一&#xff1a; 前端&#xff1a;重点是设置请求头代码如下&#xff1a; getfile(event) {//input框输入文件let file event.target.files[0];l…...

【数仓】基本概念、知识普及、核心技术

一、数仓基本概念 数仓的定义&#xff1a; 数据仓库&#xff08;Data Warehouse&#xff0c;简称DW或DWH&#xff09;是一个面向主题的、集成的、相对稳定的、反映历史变化的数据集合&#xff0c;用于支持管理决策。简言之&#xff0c;它是一个大型存储库&#xff0c;用于存储来…...

MPNet:旋转机械轻量化故障诊断模型详解python代码复现

目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...

(十)学生端搭建

本次旨在将之前的已完成的部分功能进行拼装到学生端&#xff0c;同时完善学生端的构建。本次工作主要包括&#xff1a; 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...

Opencv中的addweighted函数

一.addweighted函数作用 addweighted&#xff08;&#xff09;是OpenCV库中用于图像处理的函数&#xff0c;主要功能是将两个输入图像&#xff08;尺寸和类型相同&#xff09;按照指定的权重进行加权叠加&#xff08;图像融合&#xff09;&#xff0c;并添加一个标量值&#x…...

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...

OkHttp 中实现断点续传 demo

在 OkHttp 中实现断点续传主要通过以下步骤完成&#xff0c;核心是利用 HTTP 协议的 Range 请求头指定下载范围&#xff1a; 实现原理 Range 请求头&#xff1a;向服务器请求文件的特定字节范围&#xff08;如 Range: bytes1024-&#xff09; 本地文件记录&#xff1a;保存已…...

AI编程--插件对比分析:CodeRider、GitHub Copilot及其他

AI编程插件对比分析&#xff1a;CodeRider、GitHub Copilot及其他 随着人工智能技术的快速发展&#xff0c;AI编程插件已成为提升开发者生产力的重要工具。CodeRider和GitHub Copilot作为市场上的领先者&#xff0c;分别以其独特的特性和生态系统吸引了大量开发者。本文将从功…...

JVM暂停(Stop-The-World,STW)的原因分类及对应排查方案

JVM暂停(Stop-The-World,STW)的完整原因分类及对应排查方案,结合JVM运行机制和常见故障场景整理而成: 一、GC相关暂停​​ 1. ​​安全点(Safepoint)阻塞​​ ​​现象​​:JVM暂停但无GC日志,日志显示No GCs detected。​​原因​​:JVM等待所有线程进入安全点(如…...

Kafka入门-生产者

生产者 生产者发送流程&#xff1a; 延迟时间为0ms时&#xff0c;也就意味着每当有数据就会直接发送 异步发送API 异步发送和同步发送的不同在于&#xff1a;异步发送不需要等待结果&#xff0c;同步发送必须等待结果才能进行下一步发送。 普通异步发送 首先导入所需的k…...

Python+ZeroMQ实战:智能车辆状态监控与模拟模式自动切换

目录 关键点 技术实现1 技术实现2 摘要&#xff1a; 本文将介绍如何利用Python和ZeroMQ消息队列构建一个智能车辆状态监控系统。系统能够根据时间策略自动切换驾驶模式&#xff08;自动驾驶、人工驾驶、远程驾驶、主动安全&#xff09;&#xff0c;并通过实时消息推送更新车…...

Java中栈的多种实现类详解

Java中栈的多种实现类详解&#xff1a;Stack、LinkedList与ArrayDeque全方位对比 前言一、Stack类——Java最早的栈实现1.1 Stack类简介1.2 常用方法1.3 优缺点分析 二、LinkedList类——灵活的双端链表2.1 LinkedList类简介2.2 常用方法2.3 优缺点分析 三、ArrayDeque类——高…...