编译器对对象拷贝的优化
目录摘要一优化规则二 优化展示1在一行中的构造拷贝情况1情况2情况3:2不在一行中的拷贝拷贝3不在一行中的构造拷贝4不在一行的的构造构造三思考摘要在 C 中当类对象连续触发构造和拷贝或两者组合时编译器可能会在不改变程序语义的前提下省略某些构造或拷贝调用以提升性能。这种优化会影响构造、拷贝和析构函数的实际执行次数。因此如果我们在这些特殊成员函数中插入打印语句输出的次数可能与我们预期的逻辑次数不符。理解这种优化机制是正确解读打印结果的前提。本文将在 Visual Studio 2019 Debug 环境下进行测试重点在于建立“编译器优化确实存在且会影响行为”的认知——因为不同编译器、不同优化级别下的具体表现可能各不相同但优化本身是普遍存在的。最后解读编译器究竟做了什么才既能保证程序的正确允许又达到了优化的效果本篇文章涉及到临时变量机制和权限所以建议先看C中的引用一优化规则在Visual Studio 2019 Debug 环境下下会产生以下优化如下①构造构造-构造②构造拷贝构造-构造③拷贝构造拷贝构造-拷贝构造再怎么优化你都会保留构造因为如果对象都没有构造出来谈何后面的拷贝注意不同的编译器优化的效果可能会不同博主是在 VS19 下的 Debug 版本下进行演示二 优化展示以下是一个用于测试的类A其有构造函数拷贝函数赋值重载函数构析函数进入每个函数都会打印调用了一次 xx函数 的字眼这样更能显式的看出调用的次数以体现优化class A { public: //构造函数 A(int a 0) :_a(a) { cout A(int a)调用一次构造 endl; } //拷贝函数 A(const A aa) :_a(aa._a) { cout A(const A aa)调用一次拷贝 endl; } //赋值重载函数 A operator(const A aa) { cout A operator(const A aa)调用一次赋值 endl; if (this ! aa) { _a aa._a; } return *this; } //构析函数 ~A() { cout ~A()调用一次构析 endl; } private: int _a; };1在一行中的构造拷贝情况1int main() { A aa1 1; return 0; }解释①aa1是A类实例化的对象而1是整形这叫作类型转换②类型转换的规则如下③所有的类型转换都是不会影响到变量本身的所以如图中就是不会影响到1并不是1真的变成了一个对象而是1先作为参数进行了匿名构造构造出一个临时对象临时变量都是具有常属性的即不能被更改然后这个临时对象再去进行拷贝构造出了aa1用一个已存在的对象去初始化一个未存在的对象叫作拷贝构造④所以这里在同一行代码中进行构造拷贝构造所以优化成了单次的构造如下图正如前文的规则所言构造拷贝构造-构造构析是aa1出了main函数调用了Q为什么不是直接进行拷贝构造呢直接让1拷贝构造生成对象aa1不好吗为什么要生成中间变量不会压根没有中间变量的生成吧A首先直接让1拷贝构造生成对象aa1这个想法就是错的因为类型是不匹配的所以必定会发生类型转换而类型转换必定涉及到临时变量的产生不妨反向验证一下如果没有中间变量的生成那从1到aa1就应该是变量到变量那为何以下这样写不行呢A aa1 1;这样直接对生成的aa1进行了引用而已吗权限是一致但为什么就不行了呢报错如下正确写法const A aa1 1;解释因为我们引用的不是1而是这个临时变量而临时变量具有常性所以为了避免权限放大所以我们的aa1也要被const修饰从而和临时变量的权限一致才不会报错情况2void func(A aa1) { } int main() { //匿名构造仅是构造不是类型转换谈何构造加拷贝,然后再拷贝传参 func(A(2)); return 0; }解释①func(A(2))中的A(2)是一次匿名构造调用了一次构造函数②A(2)构造出来的对象作为实参会给到func函数的形参此处值传递所以调用了一次拷贝函数③所以是一次构造 拷贝但是是在同一行上连续进行的所以优化成了情况3:void func(A aa1) { } //构造拷贝构造-构造 int main() { //3 到 aa1 是隐示类型转换 先构造 在拷贝 func(3); return 0; }解释3直接作为参数给func函数的aa1接收这是类型转换如情况1中的意义先进行构造再进行拷贝所以 应该是 在一行上进行 构造 拷贝优化成了2不在一行中的拷贝拷贝A func() { A aa; return aa; } int main() { A aa1 func(); return 0; }解释func函数中 aa是一次构造return aaaa会拷贝构造出临时变量所以这是一次拷贝 A aa1 中的 aa1 还未存在此时去接受 func函数return 出来的对象这又是一次拷贝演示后两次拷贝的优化效果结果后两次拷贝优化成了一次3不在一行中的构造拷贝void func(A aa) { } int main() { A aa1(1); func(aa1); return 0; }解释①A aa1(1)是一次构造函数的调用②func(aa1)将aa1传给了func函数是一次拷贝函数的调用③所以这是 构造 拷贝函数 但是不会被优化因为其不是在一行上连续进行的结果两次构析是aa 和 aa1 离开自己的作用域调用的4不在一行的的构造构造A func() { A aa; return aa; } int main() { A aa2; aa2 func(); return 0; }解释①A aa2是调用一次构造②func 则进入函数A aa 是一次构造③return aa 是一次拷贝④函数返回的对象给 aa2 是一次赋值两个存咋的对象用 叫作赋值这个对前两次的构造进行展示因为不是一行上的连续构造所以不进行优化结果三编译器的行为上述讲解的核心在于建立这样一个认知编译器会在保证程序语义正确的前提下对构造和拷贝操作进行优化以减少不必要的临时对象开销。需要特别注意的是这种优化并非固定不变——不同的编译环境甚至同一编译器下的不同优化级别其优化策略和深度都可能存在差异。某些环境下优化程度甚至会更激进。因此我们不必深究具体环境下的优化细节但必须清楚优化是真实存在且可能影响程序行为的这是理解构造/析构调用次数的前提。下图展示无优化优化更深优化并进行解释解释①在g -fno-dlide-constructors环境下是不会作任何优化的②而在我们本文的vs2019debug环境下是会做一定程度的优化③而在vs2022的debug环境下优化程度更深理解①g -fno-dlide-constructors环境下是不会作任何优化的这意味着临时对象一定会产生那么临时对象生命周期结束时也会调用析构函数而我们上文都是有一定程度的优化的所以临时变量的析构我们无法观察理解②在我们上文中我们知道了return aaaa会拷贝构造出临时变量这是第一次拷贝 A aa1 中的 aa1 还未存在此时去接受 func函数return 出来的对象这是第二次拷贝这两次拷贝会被优化为一次拷贝换句话说不就是aa直接拷贝构造出了aa2吗但这是违背了我们所学知识的因为aa是栈上的临时变量其会随f2函数栈帧销毁而销毁怎么做到直接用aa来拷贝构造出main中的aa2呢这是因为编译器让aa在结束之前就去拷贝构造main中的aa2了所以不存在类似野指针的问题理解③vs2022的debug环境下我们运行代码打印如下这意味着在这种更深程度的优化下我们连一次拷贝构造函数都没有调用那问题来了现在我们变量是aa和aa2那么这个仅有的构造调用是构造的哪个变量没有拷贝构造又是怎么生成另一个变量的所以我们当前暂且认为仅有的调用的构造函数是指的f2函数中aa的构造而aa2不知道怎么产生的❓️那编译器到底是怎么做的呢编译器做法直接构造出了aa2然后让f2中的aa作为aa2的引用这样只需调用一次构造其次aa2在后续代码也可以被使用❓️怎么验证呢很简单打印二者地址即可A f2() { A aa; cout aa endl; return aa; } int main() { A aa2 f2(); cout aa2 endl; return 0; }❓️为什么编译器选择构造main中的aa2然后让aa作为aa2的引用而不是构造aa让aa2作为aa的引用呢因为如果构造aa而aa是栈上的临时变量你aa2引用一个会随f2函数栈帧销毁而销毁的变量会出现类似野指针的问题导致后续aa2的使用会导致报错所以当然选择构造aa2所以正如我们前文所言“编译器会在保证程序语义正确的前提下对构造和拷贝操作进行优化以减少不必要的临时对象开销。”编译器不管怎么优化一定都是并且程序语义正确所以我们担心的问题都不存在编译器会采取一些特殊的措施在保证正确前提下还达到了优化的效果 [ 作者 ] shylyly [ 首次发布 ] 2024.10.16❌ [ 最新修改 ] 2026.7.23 [ 声明 ] 由于笔者水平有限文中难免有疏漏或不妥之处还望读者不吝赐教

相关新闻

LosslessCut终极指南:快速无损视频剪辑的完整解决方案

LosslessCut终极指南:快速无损视频剪辑的完整解决方案

LosslessCut终极指南:快速无损视频剪辑的完整解决方案 【免费下载链接】lossless-cut The swiss army knife of lossless video/audio editing 项目地址: https://gitcode.com/gh_mirrors/lo/lossless-cut 你是否曾经面对几个小时的行车记录仪视频&#xff0…

2026/10/17 4:31:24 阅读更多 →
QKeyMapper:让Windows游戏手柄键盘鼠标无缝切换的终极解决方案

QKeyMapper:让Windows游戏手柄键盘鼠标无缝切换的终极解决方案

QKeyMapper:让Windows游戏手柄键盘鼠标无缝切换的终极解决方案 【免费下载链接】QKeyMapper [按键映射工具] QKeyMapper,Qt开发Win10&Win11可用,不修改注册表、不需重新启动系统,可立即生效和停止。支持游戏手柄映射到键鼠&am…

2026/10/15 23:41:54 阅读更多 →
御坂翻译器:3分钟开启无障碍游戏体验的终极解决方案

御坂翻译器:3分钟开启无障碍游戏体验的终极解决方案

御坂翻译器:3分钟开启无障碍游戏体验的终极解决方案 【免费下载链接】MisakaTranslator 御坂翻译器—Galgame/文字游戏/漫画多语种实时机翻工具 项目地址: https://gitcode.com/gh_mirrors/mi/MisakaTranslator 你是否曾因为语言障碍而错过心仪的日系视觉小说…

2026/10/15 23:41:22 阅读更多 →

最新新闻

HarmonyOS实战:算术闯关App串联状态管理、路由与组件生命周期

HarmonyOS实战:算术闯关App串联状态管理、路由与组件生命周期

做了几十个独立的小功能之后,很多人会遇到一个共同的瓶颈:单个知识点都看得懂,但真要组合成一个完整应用,脑子里还是一团浆糊。我身边就有同事卡在“组件会写了,路由也会跳了,状态管理背了一堆概念&#xf…

2026/10/17 5:49:30 阅读更多 →
方向贴图解码原理与跨平台实战避坑指南

方向贴图解码原理与跨平台实战避坑指南

1. 这张“灰蒙蒙”的贴图,到底在告诉引擎什么?第一次在某跨平台渲染项目里看到DecodeDirectionalLightmap这个函数名时,我下意识以为它只是个简单的颜色解码——把压缩过的RGB值还原成线性空间的光照强度。直到某天,场景中一个金属…

2026/10/17 5:49:30 阅读更多 →
斗地主计分器JS实现:状态机驱动的规则建模与防错设计

斗地主计分器JS实现:状态机驱动的规则建模与防错设计

1. 为什么一个斗地主计分器值得花时间手写JS实现“js实现斗地主计分器”——看到这个标题,很多人第一反应是:不就是加减乘除吗?用Excel不就完事了?我最初也这么想。直到去年帮某高校信息学院的棋牌类通识课助教做教学支持时&#…

2026/10/17 5:49:30 阅读更多 →
Vue面试核心原理与高频题拆解:响应式、虚拟DOM、diff与组件通信

Vue面试核心原理与高频题拆解:响应式、虚拟DOM、diff与组件通信

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 5:49:30 阅读更多 →
串联锂电池均衡的工程真相:芯片、MCU与热电耦合闭环

串联锂电池均衡的工程真相:芯片、MCU与热电耦合闭环

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 5:49:30 阅读更多 →
Spring Boot+Shiro+CAS单点登录生产实践:多应用与分布式会话

Spring Boot+Shiro+CAS单点登录生产实践:多应用与分布式会话

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 5:48:30 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/17 1:44:59 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 21:54:38 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 3:49:43 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →