C++ 返回值优化(Return Value Optimization)

Intro

返回值优化(Return Value Optimization, RVO)是 C++中的一种编译器优化技术, 它允许编译器在某些情况下省略临时对象的创建和复制/移动操作, 从而提高程序性能. RVO 主要应用于函数返回值的场景.

两种形式的 RVO

假定我们有这样一个类:

class MyClass {std::string name_;public:void SetName(std::string name) { name_ = name; }MyClass() { fmt::println("默认构造函数"); }MyClass(std::string name) : name_(name) {fmt::println("构造函数: {}", name);}MyClass(const MyClass& rhs) : name_(rhs.name_) {fmt::println("拷贝构造函数: {}", rhs.name_);}MyClass(MyClass&& rhs) noexcept {name_ = std::move(rhs.name_);rhs.name_ = name_ + "[MOVED]";fmt::println("移动构造函数: {}", name_);}MyClass& operator=(const MyClass& rhs) {fmt::println("拷贝赋值运算符");return *this;}MyClass& operator=(MyClass&&) noexcept {fmt::println("移动赋值运算符");return *this;}~MyClass() { fmt::println("析构函数, name={}", name_); }
};
  1. 命名返回值优化(Named Return Value Optimization, NRVO): 当一个函数返回一个局部变量时, 如果这个变量的类型与函数返回类型相同或可转换, NRVO 允许编译器直接在调用者的作用域内构造该局部变量, 而不是先构造然后复制到返回值.

    MyClass NamedRVO(bool useFirst) {MyClass result("named RVO");return result;
    }
    
  2. 无名返回值优化(Unamed Return Value Optimization): 当返回一个临时对象时, 编译器可以在调用者的空间直接构造这个临时对象, 避免了临时对象的生成以及后续的复制/移动操作.

    MyClass UnamedRVO() {return MyClass("unamed RVO");
    }
    

我们使用如下的测试代码, 有兴趣的读者可以打开运行(CSDN不适用, 可以访问个人网站版本):

int main(int n, char** args) {MyClass unamed = UnamedRVO();fmt::println("=======");MyClass named = NamedRVO(true);
}

输出:

构造函数
=======
默认构造函数
析构函数, name=named RVO
析构函数, name=unamed RVO

从析构函数的调用次数我们可以判断出使用了 RVO, 因为没有临时变量的产生. 直接在返回值所在的地方生成对象, 省略了返回值的拷贝或者移动.

为了对比, 我们再看一下没有启用 RVO 的输出:

构造函数: unamed RVO
移动构造函数: unamed RVO
析构函数, name=unamed RVO[moved]
移动构造函数: unamed RVO
析构函数, name=unamed RVO[moved]
=======
构造函数: named RVO
移动构造函数: named RVO
析构函数, name=named RVO[moved]
移动构造函数: named RVO
析构函数, name=named RVO[moved]
析构函数, name=named RVO
析构函数, name=unamed RVO

当启用了 RVO 后, 我们看到程序实际上是在返回值所在的地方构造了一个对象, 不需要借助拷贝或者移动.

RVO

RVO 的发展历程

  1. 从 C++98 开始, 编译器被允许做 RVO 优化
  2. 从 C++7 开始, 编译器被强制要求做 RVO 优化(Mandatory Copy Elision)

RVO 可以被禁用, 在编译的时候指定 -fno-elide-constructors(GCC/Clang) 来禁用 RVO.

C++17 中的改进

从 C++17 开始, 复制省略成为了标准的一部分, 这意味着即使类的复制/移动构造函数有副作用(如打印信息), 编译器也允许跳过这些步骤, 直接构造返回的对象. 这使得 RVO 不仅是一个优化选项, 而且是语言的一个特性, 进一步提高了代码的效率和简洁性.

RVO 失效的情况

下面的情况下 RVO 不会被触发.

  1. 编译器选项设置了 -fno-elide-constructors

  2. 函数返回的是一个全局变量:

    MyClass global("global");
    MyClass NoRVO1() { return global; }
    
  3. 当返回类型不匹配时:

    class Child : public MyClass {
    public:Child() : MyClass("child") { fmt::println("child"); }
    };MyClass NoRVO2() { return Child(); }
    
  4. 如果返回的可能是不同的对象, 那么编译器将无法确定哪个对象应该被返回, 因此无法触发 RVO.

    多个 return 语句

    MyClass NoRVO3(int x) {MyClass r1("r1");MyClass r2("r2");if (x > 0) {return r2;}return r1;
    }
    

    或者单个 return 语句里面有条件分支

    MyClass NoRVO4(int x) {MyClass r1("r1");MyClass r2("r2");return x > 0 ? r2 : r1;
    }
    
  5. 加了一个不必要的std::move. 这属于画蛇添足了, RVO 比起 move 来更高效.

    MyClass NoRVO5() {MyClass r1("r1");return std::move(r1);
    }
    

RVO 与 std::move

上面讲到std::move会导致 RVO 失效, 那么或许有人会问: 已经存在 move 了那 RVO 还有必要吗?

实际上是有必要的. 因为 RVO 是在返回位置处之间创建对象, 而 move 是先创建一个临时变量, 再进行 move. 明显多做了一步, 这一步无论再小也是代价. 另外对于 POD 类型来说, move 就是拷贝.

下面做了一个测试对比, 我们看看 move 和 RVO 的性能差别:

#include <benchmark/benchmark.h>
#include <fmt/core.h>#include <string>//{
class SimpleClass {std::string name_;public:SimpleClass(std::string name) : name_(name) {}
};SimpleClass UnamedRVO() { return SimpleClass("test string"); }SimpleClass Move() { return std::move(SimpleClass("test string")); }void BM_UnamedRVO(benchmark::State& state) {for (auto _ : state) {SimpleClass unamed = UnamedRVO();benchmark::DoNotOptimize(unamed);}
}void BM_Move(benchmark::State& state) {for (auto _ : state) {SimpleClass moved = Move();benchmark::DoNotOptimize(moved);}
}BENCHMARK(BM_UnamedRVO);
BENCHMARK(BM_Move);BENCHMARK_MAIN();
//}

测试结果(Release 版本):

-------------------------------------------------------
Benchmark             Time             CPU   Iterations
-------------------------------------------------------
BM_UnamedRVO       5.25 ns         5.24 ns    116932087
BM_Move            10.7 ns         10.6 ns     68695102

请注意在使用 benchmark 库的时候, 需要使用benchmark::DoNotOptimize来避免编译器优化掉代码. 因为unamedmoved都是局部变量, 编译器可能会优化掉它们的创建和销毁. 就会出现运行开销为 0 的谬误.

-------------------------------------------------------
Benchmark             Time             CPU   Iterations
-------------------------------------------------------
BM_UnamedRVO      0.000 ns        0.000 ns   1000000000000
BM_Move            10.1 ns         10.1 ns     66627774

参考链接

  • 演示示例源码
  • C++ RVO: Return Value Optimization for Performance in Bloomberg C++ Codebases - Michelle Fae D’Souza

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/34504.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

C++内存管理(复习)

1.动态申请多个某类型的空间并初始化 //动态申请10个int类型的空间并初始化为0到9int* p new int[10]{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; delete[] p; //销毁 2.new/delete new:开空间构造函数 delete:析构函数释放空间 new和delete是用户进行动态内存申请和释放的操作符&#…

计算机视觉——深入理解卷积神经网络与使用卷积神经网络创建图像分类算法

引言 卷积神经网络&#xff08;Convolutional Neural Networks&#xff0c;简称 CNNs&#xff09;是一种深度学习架构&#xff0c;专门用于处理具有网格结构的数据&#xff0c;如图像、视频等。它们在计算机视觉领域取得了巨大成功&#xff0c;成为图像分类、目标检测、图像分…

Java数据结构第二十三期:Map与Set的高效应用之道(二)

专栏&#xff1a;Java数据结构秘籍 个人主页&#xff1a;手握风云 目录 一、哈希表 1.1. 概念 1.2. 冲突 1.3. 避免冲突 1.4. 解决冲突 1.5. 实现 二、OJ练习 2.1. 只出现一次的数字 2.2. 随机链表的复制 2.3. 宝石与石头 一、哈希表 1.1. 概念 顺序结构以及平衡树中…

OSPF | LSDB 链路状态数据库 / SPF 算法 / 实验

注&#xff1a;本文为 “OSPF | LSDB / SPF ” 相关文章合辑。 LSDB 和 SPF 算法 潇湘浪子的蹋马骨汤 发布 2019-02-15 23:58:46 1. 链路状态数据库 (LSDB) 链路状态协议除了执行洪泛扩散链路状态通告&#xff08;LSA&#xff09;以及发现邻居等任务外&#xff0c;其第三个任…

Android Framework 之了解系统启动流程二

Android Framework 源码阅读系列篇章有&#xff1a; 系统启动流程一之init进程和zygote进程启动分析系统启动流程二之SystemServer进程启动分析 1. SystemServer 进程启动分析 在 系统启动流程一之init进程和zygote进程启动分析 中分析 zygote 进程时&#xff0c;我们知道了…

阿里云企业邮箱出现故障怎么处理?

阿里云企业邮箱出现故障怎么处理&#xff1f; 以下是处理阿里云企业邮箱故障的详细分步指南&#xff0c;帮助您快速定位问题并恢复邮箱正常使用&#xff1a; 一、初步排查&#xff1a;确认故障范围与现象 确定影响范围 全体用户无法使用 → 可能为阿里云服务端故障或网络中断。…

Python----数据分析(Pandas二:一维数组Series,Series的创建,Series的属性,Series中元素的索引与访问)

一、一维数组Series Series&#xff1a;一维数组,与Numpy中的一维array类似。它是一种类似于一维数组的对象&#xff0c;是由一组数据(各种 NumPy 数据类型)以及一组与之相关的数据标签(即索引)组成。 仅由一组数据也可产生简单的 Series 对象&#xff0c;用值列表生成 Series …

小程序配置

注册小程序账号和安装开发工具 参考文档&#xff1a;注册小程序账号和安装开发工具https://blog.csdn.net/aystl_gss/article/details/127878658 HBuilder新建项目 填写项目名称&#xff0c;选择UNI-APP&#xff0c;修改路径&#xff0c;点击创建 manifest.json 配置 需要分别…

前端UI编程基础知识:基础三要素(结构→表现→行为)

以下是重新梳理的前端UI编程基础知识体系&#xff0c;结合最新技术趋势与实战要点&#xff0c;以更适合快速掌握的逻辑结构呈现&#xff1a; 一、基础三要素&#xff08;结构→表现→行为&#xff09; 1. HTML5 核心能力 • 语义化标签&#xff1a;<header>, <nav&g…

【eNSP实战】将路由器配置为DHCP服务器

拓图 要求&#xff1a; 为 office100 和 office200 分别配置地址池 AR1接口配置 interface GigabitEthernet0/0/0ip address 192.168.100.1 255.255.255.0 # interface GigabitEthernet0/0/1ip address 192.168.200.1 255.255.255.0 AR1路由器上创建office100地址池 [AR1…

Stable Diffusion 模型具体如何设置参数?

基础参数设置 随机种子&#xff08;seed&#xff09;&#xff1a;设置一个固定的随机种子值&#xff0c;可以确保在相同文本提示下生成相同的图像。如果设置为-1&#xff0c;则每次生成的图像都是随机的。 num_inference_steps&#xff1a;控制模型推理的步数。步数越多&#…

阿里云服务器购买及环境搭建宝塔部署springboot和vue项目

云服务器ECS_云主机_服务器托管_计算-阿里云 一、前言 对于新手或者学生党来说&#xff0c;有时候就想租一个云服务器来玩玩或者练练手&#xff0c;duck不必花那么多钱去租个服务器。这些云服务厂商对学生和新手还是相当友好的。下面将教你如何快速搭建自己的阿里云服务器&…

ABAP语言的动态编程(4) - 综合案例:管理费用明细表

本篇来实现一个综合案例&#xff1a;管理费用明细表。报表在实际项目中&#xff0c;也有一定的参考意义&#xff0c;一方面展示类似的报表&#xff0c;比如管理费用、研发费用等费用的明细&#xff0c;使用业务比较习惯的展示格式&#xff1b;另一方面正好综合运用前面学习的动…

【Python办公】Excel通用匹配工具(双表互匹)

目录 专栏导读1、背景介绍2、库的安装3、核心代码4、完整代码总结专栏导读 🌸 欢迎来到Python办公自动化专栏—Python处理办公问题,解放您的双手 🏳️‍🌈 博客主页:请点击——> 一晌小贪欢的博客主页求关注 👍 该系列文章专栏:请点击——>Python办公自动化专…

2025-03-15 吴恩达机器学习2——线性回归模型

文章目录 1 概述1.1 案例1.2 分析 2 代价函数2.1 代价函数公式2.2 理解代价函数2.3 可视化代价函数 3 梯度下降3.1 实现步骤3.2 理解梯度下降3.3 学习率 4 最佳实践4.1 导入数据4.2 代码实现4.3 可视化 1 概述 ​ 线性回归模型是使用最广泛的学习算法&#xff0c;让我们从一个…

Webpack 前端性能优化全攻略

文章目录 1. 性能优化全景图1.1 优化维度概览1.2 优化效果指标 2. 构建速度优化2.1 缓存策略2.2 并行处理2.3 减少构建范围 3. 输出质量优化3.1 代码分割3.2 Tree Shaking3.3 压缩优化 4. 运行时性能优化4.1 懒加载4.2 预加载4.3 资源优化 5. 高级优化策略5.1 持久化缓存5.2 模…

实验篇| CentOS 7 下 Keepalived + Nginx 实现双机高可用

为什么要做双机高可用&#xff1f;‌ 想象一下&#xff1a;你的网站突然宕机&#xff0c;用户无法访问&#xff0c;订单流失、口碑暴跌…&#x1f4b8; ‌双机热备‌就是解决这个痛点的终极方案&#xff01;两台服务器互为备份&#xff0c;724小时无缝切换&#xff0c;保障业务…

C语言【内存函数】详解加模拟实现

目录&#xff1a; 1. memcpy使用和模拟实现 2. memmove使用和模拟实现 3. memset函数的使用 4. memcmp函数的使用 以上函数均包含在一个头文件<string.h>里面 一、memcpy的使用和模拟实现。 memcpy函数介绍&#xff1a; 函数原型&#xff1a; void * memcpy ( void…

Flutter——Android与Flutter混合开发详细教程

目录 1.创建FlutterModule项目&#xff0c;相当于Android项目里面的module库&#xff1b;2.或者编辑aar引用3.创建Android原生项目3.直接运行跑起来 1.创建FlutterModule项目&#xff0c;相当于Android项目里面的module库&#xff1b; 2.或者编辑aar引用 执行 flutter build a…

Windows根据文件名批量在文件夹里查找文件并复制出来,用WPF实现的详细步骤

项目前言 在日常工作和生活中&#xff0c;我们常常会遇到需要从大量文件中根据文件名批量查找特定文件并复制到指定位置的情况。手动一个个查找和复制文件不仅效率低下&#xff0c;还容易出错。使用 Windows Presentation Foundation (WPF) 可以创建一个用户友好的图形界面应用…