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没有分配空间 。 如下图:
如果不支持空基类优化,上面程序的输出结果不同。布局如下图:
再看个例子:
#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++布局规则不允许的

对空基类优化进行限制的根据原因在于:我们需要能比较两个指针是否指向同一对象。由于指针几乎总是用地址内部表示,所以我们必须保证两个不同的地址(即两个不同的指针)对应两个不同的对象。
这个限制也许看起来不是非常重要。然而,在实践中经常会遇到相关问题,因为许多类往往继承自某些空类的一个小集合,而这些空类又往往定义了一些共同的类型别名。当这样的类的两个子对象被用在同一个完整对象中时,优化就会被阻止。
就算有此限制,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 中每个对象的实例都可以通过取地址运算符获取其在内存布局中的开始位置,因此每个类…...
【生成式AI】ChatGPT 原理解析(2/3)- 预训练 Pre-train
Hung-yi Lee 课件整理 预训练得到的模型我们叫自监督学习模型(Self-supervised Learning),也叫基石模型(foundation modle)。 文章目录 机器是怎么学习的ChatGPT里面的监督学习GPT-2GPT-3和GPT-3.5GPTChatGPT支持多语言…...
Day03:Web架构OSS存储负载均衡CDN加速反向代理WAF防护
目录 WAF CDN OSS 反向代理 负载均衡 思维导图 章节知识点: 应用架构:Web/APP/云应用/三方服务/负载均衡等 安全产品:CDN/WAF/IDS/IPS/蜜罐/防火墙/杀毒等 渗透命令:文件上传下载/端口服务/Shell反弹等 抓包技术:…...
C++多线程同步(上)
多线程同步 引言总述详情互斥锁示例运行结果分析条件变量示例一实现分析优化运行结果示例二实现代码运行结果示例三实现代码运行结果读写锁示例实现代码注意分析运行结果附言实现运行结果运行结果个人心得引言 项目中使用多线程,会遇到两种问题,一种是对共享资源的访问时需要…...
猜猜心里数字(个人学习笔记黑马学习)
1.定义一个变量,数字类型,内容随意 2.基于input语句输入猜想的数字,通过if和多次elif的组合,判断猜想数字是否和心里数字一致 num5if int(input("请输入第一次猜想的数字:"))5:print("猜对了࿰…...
实用Pycharm插件
Pycharm的离线安装:https://plugins.jetbrains.com/ 需要根据对应的Pycharm/Goland版本选取所需的 对于实用的插件如下: 实时查看每一行的git blame信息: Gittoolbox 转换IDE的英文为中文:Chinese IDE侧格式化json字符串&#…...
数据结构试题练习
(1). 假如队列未满,现有变量data需要入队,请写出表达式; if( (tail1)%SEQLEN ! head ) {seqn[tail] data;tail (tail1)%SEQLEN; } (2). 假如队列未空,现在需要从队列取一个元素并赋值给变量data,请写出表达式; if( head ! tail ) {data se…...
s-table和columns初始化不完整,造成table文件的filter报错
问题 顺藤摸瓜找errorHandler.js文件 发现文件并没有什么问题 顺藤摸瓜找index.vue文件 首先找到报错的filter,发现与columnsSetting相关 找到columnsSetting发现等于columns 返回自己使用S-table组件的地方,发现columns初始化时仅初始化为ref()未表明…...
SLA 是什么?如何实现 SLA 管理
随着业务的不断壮大,为了满足日益增长的客户需求,网络必须保持与这些需求同步。同时,为了提高最终用户的体验,运维人员/网络管理员在监控企业级网络时遇到了不少瓶颈,必须不断审查网络,以确保提供的服务质量…...
火灾安全护航:火灾监测报警摄像机助力建筑安全
火灾是建筑安全中最常见也最具破坏力的灾难之一,为了及时发现火灾、减少火灾造成的损失,火灾监测报警摄像机应运而生,成为建筑防火安全的重要技术装备。 火灾监测报警摄像机采用高清晰度摄像头和智能识别系统,能够全天候监测建筑内…...
JavaScript 基础学习笔记(五):函数、作用域、匿名函数
目录 一、函数 1.1 声明和调用 1.2 形参和实参 1.3 返回值 二、作用域 2.1 全局作用域 2.2 局部作用域 三、匿名函数 3.1 函数表达式 3.2 立即执行函数 一、函数 理解函数的封装特性,掌握函数的语法规则 1.1 声明和调用 函数可以把具有相同或相似逻辑的代…...
Qt环境配置VTK
Qt与VTK的结合为开发者提供了强大的跨平台图形界面开发能力和三维可视化处理能力。本教程旨在详细介绍如何配置Qt环境以使用VTK库,从而为开发者打造高效、强大的三维可视化应用。 一、准备工作 在开始之前,确保您的开发环境中已经安装了Qt和CMake。Qt提…...
腾讯云最新活动_腾讯云促销优惠_代金券-腾讯云官网入口
腾讯云服务器多少钱一年?62元一年起,2核2G3M配置,腾讯云2核4G5M轻量应用服务器218元一年、756元3年,4核16G12M服务器32元1个月、312元一年,8核32G22M服务器115元1个月、345元3个月,腾讯云服务器网txyfwq.co…...
如何创建自己的Spring Boot Starter并为其编写单元测试
当我们想要封装一些自定义功能给别人使用的时候,创建Spring Boot Starter的形式是最好的实现方式。如果您还不会构建自己的Spring Boot Starter的话,本文将带你一起创建一个自己的Spring Boot Starter。 快速入门 创建一个新的 Maven 项目。第三方封装的…...
数据分析---常见处理逻辑
目录 数据清洗数据转换数据聚合数据筛选增删改查(以查为例)数据清洗 去除重复值:使用DISTINCT关键字去除重复行。//这将返回一个包含所有不重复城市的结果集 SELECT DISTINCT city FROM students;处理缺失值:使用IS NULL或IS NOT NULL判断是否为空值,并使用COALESCE或CASE…...
2024-02-26(金融AI行业概览与大数据生态圈)
1.最开始的风控是怎么做的? 人审 吃业务经验 不能大批量处理,效率低下 不适用于移动互联网的金融场景 2.建模的概念 建模就是构造一个数学公式,能将我们手上有的数据输入进去,通过计算得到一些预测结果。 比如初高中学习的…...
git忽略某些文件(夹)更改说明
概述 在项目中,常有需要忽略的文件、文件夹提交到代码仓库中,在此做个笔录。 一、在项目根目录内新建文本文件,并重命名为.gitignore,该文件语法如下 # 以#开始的行,被视为注释. # 忽略掉所有文件名是 a.txt的文件. a.txt # 忽略所有生成的 java文件, *.java # a.j…...
python爬虫实战:获取电子邮件和联系人信息
引言 在数字时代,电子邮件和联系人信息成为了许多企业和个人重要的资源,在本文中,我们将探讨如何使用Python爬虫从网页中提取电子邮件和联系人信息,并附上示例代码。 目录 引言 二、准备工作 你可以使用以下命令来安装这些库&a…...
post请求同时上传文件并传递其他参数的前后端写法
最近有一需求,post请求从前端上传一个文件同时传递一个参数,多次实验后记录下两种写法: 方法一: 前端:重点是设置请求头代码如下: getfile(event) {//input框输入文件let file event.target.files[0];l…...
【数仓】基本概念、知识普及、核心技术
一、数仓基本概念 数仓的定义: 数据仓库(Data Warehouse,简称DW或DWH)是一个面向主题的、集成的、相对稳定的、反映历史变化的数据集合,用于支持管理决策。简言之,它是一个大型存储库,用于存储来…...
从WWDC看苹果产品发展的规律
WWDC 是苹果公司一年一度面向全球开发者的盛会,其主题演讲展现了苹果在产品设计、技术路线、用户体验和生态系统构建上的核心理念与演进脉络。我们借助 ChatGPT Deep Research 工具,对过去十年 WWDC 主题演讲内容进行了系统化分析,形成了这份…...
【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密
在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...
聊聊 Pulsar:Producer 源码解析
一、前言 Apache Pulsar 是一个企业级的开源分布式消息传递平台,以其高性能、可扩展性和存储计算分离架构在消息队列和流处理领域独树一帜。在 Pulsar 的核心架构中,Producer(生产者) 是连接客户端应用与消息队列的第一步。生产者…...
全球首个30米分辨率湿地数据集(2000—2022)
数据简介 今天我们分享的数据是全球30米分辨率湿地数据集,包含8种湿地亚类,该数据以0.5X0.5的瓦片存储,我们整理了所有属于中国的瓦片名称与其对应省份,方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...
江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命
在华东塑料包装行业面临限塑令深度调整的背景下,江苏艾立泰以一场跨国资源接力的创新实践,重新定义了绿色供应链的边界。 跨国回收网络:废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点,将海外废弃包装箱通过标准…...
基于Docker Compose部署Java微服务项目
一. 创建根项目 根项目(父项目)主要用于依赖管理 一些需要注意的点: 打包方式需要为 pom<modules>里需要注册子模块不要引入maven的打包插件,否则打包时会出问题 <?xml version"1.0" encoding"UTF-8…...
vue3 定时器-定义全局方法 vue+ts
1.创建ts文件 路径:src/utils/timer.ts 完整代码: import { onUnmounted } from vuetype TimerCallback (...args: any[]) > voidexport function useGlobalTimer() {const timers: Map<number, NodeJS.Timeout> new Map()// 创建定时器con…...
Android15默认授权浮窗权限
我们经常有那种需求,客户需要定制的apk集成在ROM中,并且默认授予其【显示在其他应用的上层】权限,也就是我们常说的浮窗权限,那么我们就可以通过以下方法在wms、ams等系统服务的systemReady()方法中调用即可实现预置应用默认授权浮…...
用docker来安装部署freeswitch记录
今天刚才测试一个callcenter的项目,所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...
Maven 概述、安装、配置、仓库、私服详解
目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...
