新闻详情

新闻详情

首页 / 资讯中心 / 详情

RVO 与 NRVO:为什么 return std::move(x) 是反优化

发布时间:2026/9/27 22:47:47来源:尧图网络
RVO 与 NRVO:为什么 return std::move(x) 是反优化
看过移动语义的人多半在代码里写过return std::move(x);以为「显式告诉编译器去移动肯定更快」。结果往往相反——这行「优化」反而更慢。原因藏在编译器一项叫复制省略copy elision的优化里。这篇用实测把这件事钉死什么情况下编译器会帮你把拷贝/移动整个省掉什么情况下你亲手写的std::move恰恰破坏了它。1. 引子一行「优化」为何更慢先记住一个反直觉的结论函数返回局部变量时什么都不写直接return x;往往比return std::move(x);更快。因为编译器本可以把局部对象直接构造在调用方的内存里连一次移动都不需要你一写std::move反而逼它先做移动再处理凭空多出动作。下面用构造/移动计数实测给你看。2. 复制省略少数能改变可观察行为的优化绝大多数优化不改变程序「看得见」的行为结果、输出。但复制省略copy elision是标准明确允许跳过拷贝构造和析构调用的少数例外——即使这些构造函数有副作用比如打印、计数编译器也可以不调用它们。它有两种形态名称全称触发条件标准是否强制RVOReturn Value Optimization返回纯右值prvalue如return T{};C17 起强制NRVONamed Return Value Optimization返回具名局部变量如return x;编译器可选官方文档Copy elision — cppreference关键点C17 把「返回纯右值」的省略从「可能省略」升级为「保证省略guaranteed copy elision」。也就是说return Widget{};这种写法标准保证不会调用任何拷贝/移动构造对象直接在调用方那块内存上构造。3. 实测NRVO vs return std::move(x)下面这个W在拷贝/移动构造里都打印于是「有没有真的发生构造」一眼可见// rvo_trace.cpp — 编译: g -stdc17 -Wall -O2 rvo_trace.cpp -o rt#includecstdio#includeutilitystructW{W()default;W(constW){std::puts( 拷贝构造);}W(W)noexcept{std::puts( 移动构造);}};Wby_value(){W x;returnx;}// NRVO 候选Wby_move(){W x;returnstd::move(x);}// 阻止 NRVOintmain(){std::puts(--- return x; (NRVO 候选) ---);W aby_value();std::puts(--- return std::move(x); (阻止 NRVO) ---);W bby_move();}--- return x; (NRVO 候选) --- --- return std::move(x); (阻止 NRVO) --- 移动构造结果很清楚return x;by_value里的局部x被直接构造到a的位置一次构造调用都没有return std::move(x);std::move把x变成右值破坏了 NRVO 的「返回具名变量」前提编译器只能乖乖调一次移动构造。你本想加速却硬生生塞了一次移动进去。对于std::string、std::vector这类移动也要搬内部指针的类型这多出来的一次移动虽然 O(1)但终归是白做的工更糟的是它会挡住编译器后续可能做的其他优化。4. 对象位置前后对比RVO 究竟省了什么RVO/NRVO 的本质是把「在f内部构造临时对象 → 再搬到调用方」这两步合并成「直接在调用方的内存上构造」一步。没有优化时理论上的朴素流程 RVO / NRVO 之后 调用方栈帧 调用方栈帧 ┌──────────┐ ┌──────────┐ │ ret_val │ ← 目标位置 │ ret_val │ └──────────┘ │ (x 直接 │ ▲ │ 建在这) │ │ 移动/拷贝 └──────────┘ f() 栈帧 f() 里不再有独立的 x ┌──────────┐ │ x │ 先在这里构造再搬出去 └──────────┘ 省掉了一次构造 一次析构换句话说优化后f里根本没有「独立的x对象」x就是ret_val的别名直接在目标位置出生。这正是return x;能省掉移动的原因。5. C17 强制 RVO返回纯右值如果返回的是一个纯右值prvalue——也就是「没有名字的临时对象」——C17 保证省略连 NRVO 的「可选」都不是问题// rvo_count.cpp — 编译: g -stdc17 -Wall -O2 rvo_count.cpp -o rc#includecstdio#includeutilitystructCounter{Counter()default;Counter(constCounter){copies;}Counter(Counter)noexcept{moves;}staticintcopies,moves;};intCounter::copies0;intCounter::moves0;Counternamed(){Counter x;returnx;}// NRVO理想0 次Countermoved(){Counter x;returnstd::move(x);}// 阻止 NRVO1 次移动Counterprvalue(){returnCounter{};}// C17 强制 RVO0 次intmain(){Counter anamed();Counter bmoved();Counter cprvalue();std::printf(copies%d moves%d\n,Counter::copies,Counter::moves);}copies0 moves1三种写法对比一目了然返回方式return x;return std::move(x);return T{};标准保证NRVO编译器通常做无强制移动C17 强制 RVO实测构造/移动次数01 次移动0结论✅ 最好❌ 最差✅ 同样好named()和prvalue()都做到零拷贝零移动唯独画蛇添足的moved()多花了一次移动。6. 想亲眼看到「被省略的构造」用 -fno-elide-constructors既然省略会把构造调用藏起来怎么确认它真的存在过GCC/Clang 提供一个开关-fno-elide-constructors关掉复制省略后所有本该被省略的拷贝/移动都会实打实地发生方便你对照学习。g-stdc17 -fno-elide-constructors rvo_count.cpp-orc_noelide ./rc_noelide# 关闭省略后named() 也会多出一次移动局部 x - 返回槽注意这只是教学观察工具发布代码时不要加它——省略是白送的性能关掉只会变慢。官方文档Copy elision — cppreference含 -fno-elide-constructors 说明7. 完整示例一个能整体跑的程序把前几节合起来下面这段覆盖三种返回方式并顺带演示「返回函数参数/成员变量时std::move才有意义」// rvo_full.cpp — 编译: g -stdc17 -Wall -O2 rvo_full.cpp -o rf#includecstdio#includeutilitystructW{W()default;W(constW){std::puts( 拷贝构造);}W(W)noexcept{std::puts( 移动构造);}};Wgood(){W x;returnx;}// 正确依赖 NRVOWbad(){W x;returnstd::move(x);}// 反例不要这么写阻止 NRVOWtake_param(W p){returnp;}// 返回的是参数不是局部变量intmain(){std::puts(good():);W agood();std::puts(bad():);W bbad();std::puts(take_param(传临时):);W ctake_param(W{});// 参数是入参move 它才是合理的}good(): bad(): 移动构造 take_param(传临时): 移动构造good()零开销bad()多了一次移动take_param接收的是入参不是函数内的具名局部变量不在 NRVO 范围内这时return p;实际上也会因 NRVO 不适用而移动所以传参场景用std::move才有意义——但注意这里return p;已经能正确移动了多数情况下连std::move都不必写。8. 延伸阅读Copy elision — cppreference —— 强制 RVO 与 NRVO 的权威定义含-fno-elide-constructorsGotW #39: Parameter Evaluation Order —— Sutter 关于返回值优化的经典问答Compiler Explorer —— 对比return x与return std::move(x)生成的汇编移动指令肉眼可见同系列《std::move 其实什么都没搬》—— 先搞懂std::move只是类型转换本文才讲得通9. 一句话总结复制省略允许编译器跳过拷贝/移动构造C17 起返回纯右值是强制省略返回具名局部变量靠 NRVO编译器通常也会做。return std::move(x)把具名变量变成右值亲手破坏了 NRVO反而多一次移动——所以返回局部变量时千万别画蛇添足写std::move。
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

基于 SSM + MyBatis-Plus + MySQL 的丰泽助农专区平台设计与实现 2026/9/27 23:34:39

基于 SSM + MyBatis-Plus + MySQL 的丰泽助农专区平台设计与实现

基于 SSM MyBatis-Plus MySQL 的丰泽助农专区平台设计与实现 一、前言 助农这件事,难点往往不在"种得好",而在"卖得稳"。产地不缺好货,缺的是一个把农户、合作社和消费者直接连起来、还能持续运营的线上专区。本文分…

阅读更多 →
建立一个网站多少钱?避坑指南与预算拆解 2026/9/27 23:34:32

建立一个网站多少钱?避坑指南与预算拆解

建立一个网站多少钱?避坑指南与预算拆解 域名买错了,服务器选小了,SSL证书忘了续,这三样搞不懂,你的网站上线就是灾难现场。很多老板问【建立一个网站多少钱】,其实这钱不是花在“建”上,是花在这些【注意事项】里。别被“99元建站”忽悠了,也别…

阅读更多 →
3个真相解析rediswordpress内存对建站报价影响 2026/9/27 23:34:32

3个真相解析rediswordpress内存对建站报价影响

3个真相解析rediswordpress内存对建站报价影响 自己不会代码想做网站,最头疼的就是看到“Redis”、“WordPress”、“内存”这些词就晕。很多老板拿着预算表问建站报价,结果发现不同公司给出的价格差距巨大,有的几千元,有的…

阅读更多 →
美团三合一系统源码带商家系统搭建实战与避坑指南 2026/9/27 23:34:32

美团三合一系统源码带商家系统搭建实战与避坑指南

简介:一套可二次开发的美团三合一系统源码,面向餐饮、零售等中小商家及开发者,提供在线接单、商家后台、菜单库存管理、数据分析等完整功能,并配套视频搭建与文字搭建教程,适合无技术背景用户快速上线,也便…

阅读更多 →
Amethyst Pong 教程(四):让球移动与反弹——ECS 组件、System 与延迟生成的完整实现 2026/9/27 23:34:26

Amethyst Pong 教程(四):让球移动与反弹——ECS 组件、System 与延迟生成的完整实现

【免费下载链接】amethyst Data-oriented and data-driven game engine written in Rust 项目地址: https://gitcode.com/gh_mirrors/ame/amethyst 点击查看 免费下载 本文是 Amethyst(Rust 数据驱动游戏引擎)Pong 教程系列的第四篇&#xf…

阅读更多 →
uber-go/fx 完整版本演进指南:从 1.0 Beta 到 1.24 的 API 变迁与源码级解读 2026/9/27 23:34:26

uber-go/fx 完整版本演进指南:从 1.0 Beta 到 1.24 的 API 变迁与源码级解读

后端开发工具 【免费下载链接】fx A dependency injection based application framework for Go. 项目地址: https://gitcode.com/gh_mirrors/fx1/fx 点击查看 免费下载 本文以仓库中的 docs/src/changelog.md(与根目录 CHANGELOG.md 内容一致&#xff…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉