c++11--左值,右值,移动语义,引用折叠,模板类型推断,完美转发
1.移动语义移动构造和移动赋值均属于移动语义范畴。移动语义的实现依赖于右值概念右值引用。1.1.一个移动构造的实例#include iostream using namespace std; class HasPtrMem{ public: HasPtrMem():d(new int(3)){ cout Construct: n_cstr endl; } HasPtrMem(const HasPtrMem h):d(new int(*h.d)){ cout Copy construct: n_cptr endl; } HasPtrMem(HasPtrMem h):d(h.d){ h.d nullptr; cout Move construct: n_mvtr endl; } ~HasPtrMem(){ if(d) { delete d; } cout Destruct: n_dstr endl; } int *d; static int n_cstr; static int n_dstr; static int n_cptr; static int n_mvtr; }; int HasPtrMem::n_cstr 0; int HasPtrMem::n_dstr 0; int HasPtrMem::n_cptr 0; int HasPtrMem::n_mvtr 0; HasPtrMem GetTemp(){ HasPtrMem h;// 1.局部对象构造 cout Resource from __func__ : hex h.d endl; return h; }// 3.局部对象析构 int main() { // 2.匿名对象拷贝构造--实参为即将析构的局部变量时匹配到右值版本 // 4.对象a拷贝构造实参为匿名临时对象时匹配到右值版本 HasPtrMem a GetTemp(); // 3.匿名对象析构 cout Resource from __func__ : hex a.d endl; // 4.对象a析构 }对其执行g -stdc11 44.cpp -fno-elide-constructors -o 44再执行./.44。上述以函数内临时变量为实参拷贝构造匿名临时变量以匿名临时变量为实参拷贝构造变量a时均触发了移动拷贝构造。移动拷贝构造的形参是一个右值引用类型。我们需要先理解(1). 左值右值。(2). 左值引用常量左值引用右值引用作为形参时可接收的实参。及各类实参和形参匹配的优先级。才能较好的把握移动拷贝移动赋值的使用。从理解上来说针对需要管理资源的类型我们将拷贝和赋值区分为浅拷贝浅赋值和深拷贝深赋值。浅指的是我们通过窃取目标对象资源来完成资源转移。深指的是我们自己产生资源然后使其和目标对象内容一致。这种场景下类型通过提供移动拷贝移动赋值来实现浅拷贝浅赋值。通过提供拷贝构造赋值来实现深拷贝深赋值。1.2.左值右值c程序中所有的值必属于左值将亡值纯右值三者之一。将亡值纯右值均属于右值范畴。(1). 纯右值和C中右值概念一致。a. 非引用返回的函数返回的匿名临时变量值。b. 一些运算表达式诸如1 3产生的匿名临时变量值。c. 不跟对象关联的字面量值比如2、c、true。d. 类型转换函数返回的匿名临时变量值。e.lambda表达式返回的匿名临时变量值。(2). 将亡值C特有。a. 返回右值引用T的函数返回值–匿名右值引用类型。b.std::move的返回值–匿名右值引用类型。c. 转换为T的类型转换函数的返回值–匿名右值引用类型。(3). 左值排除(1)(2)后的可标识函数对象值都属于左值。1.3.非常量左值引用常量左值引用非常量右值引用常量右值引用常量和非常量左值和右值是可以同时用于修饰对象的两类性质。右值存在的价值一个是用于实现移动语义一个是用于实现完美转发。一般常量右值引用没有存在的意义因为(1). 右值用于移动语义时我们窃取其资源后需要对其修改以便阻止资源多次释放野指针等问题。(2). 如果需要引用右值且让右值不可通过引用更改直接使用常量左值来引用即可。使用右值作为实参来初始化右值引用常量左值引用后右值的生命期会被延长。通过右值引用可以对引用对象进行修改通过常量左值引用无法对引用对象进行修改。#include iostream class A { public: A() { printf(A()_%x\n, this); } A(const A a) { printf(A(const A_%x)\n, this); } ~A() { printf(~A()_%x\n, this); } public: int i 0; }; A fun() { A a; return a; } int main() { // 这里的变量除了a1还有一个隐藏起来的匿名临时变量。 // 汇编执行时 // 是匿名临时变量先接受了函数的返回值 // 再通过匿名临时变量完成了a1的初始化 // 这里函数内部临时变量a,变量a1均是左值 // 匿名临时变量是右值 A a1 fun(); printf(tag1\n); A a2 fun(); printf(tag2\n); a2.i 1; const A a3 fun(); printf(tag3\n); return 0; }上述实例反映了两点(1). 通过采用右值引用或常量左值引用接受函数fun的非引用类型返回值。使得返回值对应的匿名临时右值对象的生命期延长了。(2). 通过右值引用可以修改所引用变量通过常量左值引用则不可。以下表格整理了引用类型及每种类型可以接受的初始化实参类型。引用类型可以引用的值类型注记非常量左值引用非常量左值常量左值引用非常量左值常量左值非常量右值常量右值全能类型用于深拷贝非常量右值引用非常量右值移动语义完美转发常量右值引用非常量右值常量右值1.4.std::move#include iostream class A { public: A():m_c(a){ printf(A()_%x\n, this); } A(A a):m_c(a.m_c){ printf(A()_%x\n, this); fun(a); } A(const A a):m_c(a.m_c){ printf(A(const)_%x\n, this); fun(a); } A(A a):m_c(a.m_c){ printf(A()_%x\n, this); fun(a); } A(const A a):m_c(a.m_c) { printf(const A()_%x\n, this); fun(a); } A operator(A a) { printf((A)_%x\n, this); m_c a.m_c; fun(a); return *this; } A operator(const A a) { printf((const A)_%x\n, this); m_c a.m_c; fun(a); return *this; } A operator(A a) { printf((A)_%x\n, this); m_c a.m_c; fun(a); return *this; } A operator(const A a) { printf((const A)_%x\n, this); m_c a.m_c; fun(a); return *this; } void fun(A a) { printf(fun(A)_%x\n, this); } void fun(const A a) { printf(fun(const A)_%x\n, this); } void fun(A a) { printf(fun(A)_%x\n, this); } void fun(const A a) { printf(fun(constA)_%x\n, this); } ~A() { printf(~A()_%x\n, this); } private: char m_c; }; void funn(A a) { } int main() { A a; const A a2; printf(tag\n); // std::move针对非常量左值返回非常量右值引用。 // 非常量右值引用虽然是一个非常量右值的别名但由于有了名字。所以右值引用被当成一个左值。 // 故通过非常量右值引用调用fun时匹配到非常量左值引用版本。 A a3 std::move(a); printf(tag1\n); // std::move针对常量左值返回常量右值引用。 // 常量右值引用虽然是一个常量右值的别名但由于有了名字。所以右值引用被当成一个左值。 // 故通过常量右值引用调用fun时匹配到常量左值引用版本。 A a4 std::move(a2); printf(tag2\n); // std::move针对非常量右值返回非常量右值引用。 // 非常量右值引用虽然是一个非常量右值的别名但由于有了名字。所以右值引用被当成一个左值。 // 故通过非常量右值引用调用fun时匹配到非常量左值引用版本。 A a5 std::move(A()); printf(tag3\n); A aa1 a; // std::move针对非常量左值引用返回非常量右值引用。 A aaa1 std::move(aa1); printf(tag4\n); const A aa2 a2; // std::move针对常量左值引用返回常量右值引用。 A aaa2 std::move(aa2); printf(tag5\n); A aa3 A(); // std::move针对非常量右值引用返回非常量右值引用。 A aaa3 std::move(aa3); printf(tag6\n); const A aa4 A(); // std::move针对常量右值引用返回常量右值引用。 A aaa4 std::move(aa4); printf(tag7\n); A b; funn(b); printf(tag5\n); return 0; }针对std::move简单的总结是(1). 针对左值实参左值引用实参会返回此实参的右值引用类型。(2). 针对右值实参右值引用实参会返回此实参的右值引用类型。(3). 保持实参的常量属性不变。注意点(1). 右值引用虽然是一个右值的引用但由于引用本身是有名字的所以右值引用是一个左值类型。(2).std::move返回实参的右值引用但这里相当于一个得到了匿名右值引用。所以将此返回值传参调用函数时右值引用会被当成右值类型。而非(1)中的左值类型。关于std::move保持实参的常量属性不变的辅助实例#include iostream void fun(int* a) { printf(int* \n); } void fun(const int* a) { printf(const int* \n); } void fun(int* const a) { printf(int* const \n); } void fun(const int* const a) { printf(const int* const \n); } int main() { int a1 10; const int a11 10; int *a a1; const int* a2 a11; int * const a3 a1; const int* const a4 a11; fun(std::move(a)); fun(std::move(a2)); fun(std::move(a3)); fun(std::move(a4)); return 0; }#includeiostream#includevectorvoidfun(constinta){printf(const int\n);}voidfun(inta){printf(int\n);}intmain(){inti0;fun(std::move(i));intjstd::move(i);fun(j);return0;}1.5.编译器的返回值优化上述各个观察函数返回值的实例我们编译时加了-fno-elide-constructors选项来关闭编译器针对返回值的优化。当开启此优化时#include iostream class A { public: A(){ printf(A()_%x\n, this); } A(Aa){ printf(A()_%x\n, this); } A(const A){ printf(A(const A)_%x\n, this); } A(A){ printf(A()_%x\n, this); } A(const A){ printf(A(const)_%x\n, this); } ~A(){ printf(~A()_%x\n, this); } }; A fun() { A a; return a; } int main() { A b fun(); return 0; }b直接霸占了fun内部的变量a。此优化并不是对任何情况均有效。还有一些情形即使存在此优化也不能达到最好效果。而移动语义总是可以在显式控制下采用最有效的方法实现目标。2.完美转发完美转发指的是在函数模板中完全依照模板参数的类型将参数传递给函数模板中调用的另外一个函数。2.1.引用折叠为了支持完美转发c既需要引入右值右值引用。也需要引入引用折叠。引用折叠进一步分为左值引用折叠右值引用折叠。// TR的类型定义为T声明v的类型为TR - v的实际类型为T又因为T为const int所以v的实际类型为const int所以TR v 1;对应const int v 1; #include iostream typedef const int T; typedef T TR; int main() { TR v 1; }TR的类型定义声明v的类型v的实际类型TTRTTTRTTTRTTTRTTTRTTTRT将上述引用折叠结合模板类型推断可以实现完美转发。2.2.模板类型推断当模板的形参中对模板类型采用T的形式时推断T的类型时当转发函数的实参是类型X的一个左值或左值引用则模板参数T被推导为X类型。当转发函数的实参是类型X的一个右值或右值引用则模板参数T被推导为X类型。此时需结合引用折叠规则来最终确定参数的实际类型。一个实例templatetypename T void IamForwording(T t){ IrunCodeActually(static_castT(t)); }对于上述形式的转发函数如果我们调用转发函数时传入了一个X类型的左值或左值引用。则(1). 模板参数T被推断为X类型。(2). 引用折叠完成后的实际的转发函数是void IamForwording(X t){ IrunCodeActually(X(t)); }这样通过左值或左值引用调用转发函数最终调用实际函数的左值引用版本。如果我们调用转发函数时传入了一个X类型的右值或右值引用。则(1).模板参数T被推断为X类型。(2).引用折叠完成后的实际的转发函数是void IamForwording(X t){ IrunCodeActually(Xt); }这样通过右值或右值引用调用转发函数最终调用实际函数的右值引用版本。上述Xt并不多余因为若直接采用t的形式调用实际函数由于右值引用类型自身是左值所以将匹配到左值引用版本。2.3.完美转发实例#include iostream using namespace std; void RunCode(int m){ cout int endl; } void RunCode(int m){ cout int endl; } void RunCode(const int m) { cout const int endl; } void RunCode(const int m) { cout const int endl; } templatetypename T void PerfectForward(T t){ RunCode(forwardT(t)); } int main(){ int a; int b; const int c 1; const int d 0; PerfectForward(a); PerfectForward(move(b)); PerfectForward(c); PerfectForward(move(d)); }将上述转发函数改为如下形式一样可以达到效果templatetypename T void PerfectForward(T t){ RunCode(static_castT(t)); }若上述模板形参变成如下templatetypename T void PerfectForward(T t){ RunCode(static_castT(t)); } int main(){ int a; int b; const int c 1; const int d 0; PerfectForward(a); PerfectForward(move(b)); PerfectForward(c); PerfectForward(move(d)); }则执行PerfectForward(move(b));会报错因为此时无法完成将一个右值引用匹配到【T推断为intTt的实际类型为int】int的转换。而PerfectForward(move(d));可以执行此时是一个常量右值引用匹配到【T推断为const intTt的实际类型为const int】const int的转换然后内部执行RunCode会转变为一个const int类型参与RunCode的匹配过程。这里static_castT折叠后变为static_castconst int这里的右值引用之所以不会当成作值类似std::move返回的右值引用是因为这里得到的右值引用是一个匿名的右值引用。所以参与函数传参时不会被当成左值。为了完整我们在main中再补充一组测试printf(tag\n); int aa a; const int caa c; int aaa move(a); const int caaa move(d); PerfectForward(aa); PerfectForward(caa); PerfectForward(aaa); PerfectForward(caaa);上述补充内容对应的输出为因为这里的aaacaaa是有名字的右值引用所以参与函数传参时被当作左值对待了。

相关新闻

前端可观测性的下一站:Real User Monitoring 与 Synthetic Monitoring 的融合

前端可观测性的下一站:Real User Monitoring 与 Synthetic Monitoring 的融合

前端可观测性的下一站:Real User Monitoring 与 Synthetic Monitoring 的融合 前端监控长期处于"两套体系并行"的状态:Real User Monitoring(RUM)记录真实用户的体验数据,Synthetic Monitoring 在受控环境中…

2026/7/29 17:35:40阅读更多 →
WPF应用实战开发指南 - 如何实现动态内容展示

WPF应用实战开发指南 - 如何实现动态内容展示

在我们开发一些复杂信息的时候,由于需要动态展示一些相关信息,因此我们需要考虑一些控件内容的动态展示,可以通过动态构建控件的方式进行显示,如动态选项卡展示不同的信息,或者动态展示一个自定义控件的内容等等&#…

2026/7/29 17:35:40阅读更多 →
C/C++开发神器CLion v2023.3全新发布——支持AI辅助编码

C/C++开发神器CLion v2023.3全新发布——支持AI辅助编码

CLion是一款专为开发C及C所设计的跨平台IDE,它是以IntelliJ为基础设计的,包含了许多智能功能来提高开发人员的生产力。这种强大的IDE帮助开发人员在Linux、OS X和Windows上来开发C/C,同时它还使用智能编辑器来提高代码质量、自动代码重构并且…

2026/7/29 17:35:40阅读更多 →
prompt-tuning配置文件详解:Gin配置系统入门与高级用法

prompt-tuning配置文件详解:Gin配置系统入门与高级用法

prompt-tuning配置文件详解:Gin配置系统入门与高级用法 【免费下载链接】prompt-tuning Original Implementation of Prompt Tuning from Lester, et al, 2021 项目地址: https://gitcode.com/gh_mirrors/pr/prompt-tuning 在自然语言处理领域,pr…

2026/7/29 18:42:13阅读更多 →
膜结构球场的透光性如何?

膜结构球场的透光性如何?

《【膜结构球场】哪家好:专业深度测评排名前五》开篇:定下基调在体育设施建设中,膜结构球场因其独特的优势受到越来越多的关注,其中透光性是衡量膜结构球场性能的重要指标之一。本次测评旨在为对膜结构球场感兴趣的人群&#xff0…

2026/7/29 18:42:13阅读更多 →
为什么选择Dockerfile.vim?5大理由让你告别手动编写Dockerfile

为什么选择Dockerfile.vim?5大理由让你告别手动编写Dockerfile

为什么选择Dockerfile.vim?5大理由让你告别手动编写Dockerfile 【免费下载链接】Dockerfile.vim Vim syntax file & snippets for Dockers Dockerfile 项目地址: https://gitcode.com/gh_mirrors/do/Dockerfile.vim Dockerfile.vim是一款专为Dockerfile打…

2026/7/29 18:42:13阅读更多 →
AI客服上线首周:意图识别准确率从78%到92%的5个关键操作与Taotoken路由玄机

AI客服上线首周:意图识别准确率从78%到92%的5个关键操作与Taotoken路由玄机

从Demo到生产的鸿沟:跨越模型落地的三重障碍 去年用GPT-4搭建的客服Demo获得满堂彩,但真实流量涌入后,我们遭遇了意料之外的挑战。意图识别准确率从演示时的92%骤降至78%,暴露出Demo环境与生产环境的本质差异。具体表现为&#x…

2026/7/29 18:42:13阅读更多 →
浅谈优惠券返利 app 架构选型:单体应用向微服务演进路径

浅谈优惠券返利 app 架构选型:单体应用向微服务演进路径

浅谈优惠券返利 app 架构选型:单体应用向微服务演进路径 大家好,我是省赚客APP研发者微赚淘客! 任何一款成功的返利App,其技术架构都并非一蹴而就。它必然经历了一个从简单到复杂、从集中到分布的演进过程。今天,我们就…

2026/7/29 18:42:13阅读更多 →
抖音无水印下载终极教程:免费获取高清视频的完整解决方案

抖音无水印下载终极教程:免费获取高清视频的完整解决方案

抖音无水印下载终极教程:免费获取高清视频的完整解决方案 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback su…

2026/7/29 18:40:13阅读更多 →
覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

覆盖国产 + 海外 + 开源模型,OpenClaw 2.7.9 Windows/Mac 双端部署详解

🔹 工具基础介绍 OpenClaw 是开源生态中一款实用性较强的本地智能工具,凭借本地离线运行、可视化图形操作和任务自动化三大核心特性,赢得了众多用户的青睐。与普通在线对话AI工具不同,它属于能够直接操控本机软硬件的智能数字员工…

2026/7/29 9:47:45阅读更多 →
伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

伺服阀焊完微漏毁整机?精密激光焊接三关锁住高压

所谓液压伺服阀体的精密激光焊接,是用激光束对阀座壳体(通常为不锈钢或铝合金)进行密封焊接,使阀体在21-35MPa的高压液压油或压缩气体中长期运行而不发生介质泄漏。液压伺服阀是高端液压系统的"大脑"。从航空航天飞行控…

2026/7/29 7:00:19阅读更多 →
D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南

D2DX:三步实现《暗黑破坏神2》高清宽屏体验的终极指南 【免费下载链接】d2dx D2DX is a complete solution to make Diablo II run well on modern PCs, with high fps and better resolutions. 项目地址: https://gitcode.com/gh_mirrors/d2/d2dx 你是否还在…

2026/7/29 7:58:51阅读更多 →
28. Agent 执行到一半想暂停?用 interrupt 给它设个“关卡“!

28. Agent 执行到一半想暂停?用 interrupt 给它设个“关卡“!

28. Agent 执行到一半想暂停?用 interrupt 给它设个“关卡“! 在构建复杂的 Agent 系统时,我们经常会遇到这样的场景:Agent 正在执行一个多步骤的任务,比如“下单购买商品”,但执行到一半时,我们…

2026/7/29 0:01:46阅读更多 →
自律同行,突破无界!NANK南卡正式官宣曾舜晞成为品牌代言人

自律同行,突破无界!NANK南卡正式官宣曾舜晞成为品牌代言人

近日,国际专注开放式技术研发的声学品牌Nank南卡,正式官宣实力艺人曾舜晞担任品牌代言人。消息一经发出便轰动全网。为什么耳机品牌不选择流量明星、老牌歌手?而且是选择曾舜晞?让我们一起来探索一下!比起短期的流量&a…

2026/7/29 0:01:46阅读更多 →
【RT-DETR多模态创新改进】CVPR 2025 | 独家特征融合创新改进篇 | 引入RLAB残差线性注意力模块,有效融合并强调多尺度特征,多种改进点,适合红外与可见光融合目标检测任务,有效涨点

【RT-DETR多模态创新改进】CVPR 2025 | 独家特征融合创新改进篇 | 引入RLAB残差线性注意力模块,有效融合并强调多尺度特征,多种改进点,适合红外与可见光融合目标检测任务,有效涨点

一、本文介绍 🔥本文在RT-DETR多模态融合目标检测中引入RLAB残差线性注意力模块,可在不同模态特征交互阶段进行多次残差细化,使可见光、红外等特征在尺度、语义和空间位置上更好对齐;随后将细化特征与解码器输出拼接并生成Q、K、V,通过线性注意力自适应强化关键通道、目…

2026/7/29 0:01:46阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/28 20:22:24阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/29 4:31:51阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/29 14:26:42阅读更多 →