深入理解C++中的RVO

前言

考虑存在这样一个类如HeavyObject,其拷贝赋值操作比较耗时,通常你在使用函数返回这个类的一个对象时会习惯使用哪一种方式?或者会根据具体场景选择某一种方式?

// style 1
HeavyObject func(Args param);// style 2
bool func(HeavyObject* ptr, Args param);

上面的两种方式都能过到同样的目的,但直观上的使用体验的差别也是非常明显的:

style 1只需要一行代码,而style 2需要两行代码

// style 1
HeavyObject obj = func(params);// style 2
HeavyObject obj;
func(&obj, params);

但是,能达到同样的目的,消耗的成本却未必是一样的,这取决于多个因素,比如编译器支持的特性、C++语言标准的规范强制性、多团队多环境开发等等。

看起来style 2虽然使用时需要写两行代码,但函数内部的成本却是确定的,只会取决于你当前的编译器,外部即使采用不同的编译器进行函数调用,也并不会有多余的时间开销和稳定性问题。比如func内部使用clang+libc++编译,外部调用的编译环境为gcc+gnustl或者vc++,除了函数调用开销,不用担心其它性能开销以及由于编译环境不同会崩溃问题。

因此这里我主要剖析一下style 1背后开发者需要关注的点。

RVO

RVO是Return Value Optimization的缩写,即返回值优化,NRVO就是具名的返回值优化,为RVO的一个变种,此特性从C++11开始支持,也就是说C++98、C++03都是没有将此优化特性写到标准中的,不过少量编译器在开发过程中也会支持RVO优化(如IBM Compiler?),比如微软是从Visual Studio 2010才开始支持的。

仍然以上述的HeavyObject类为例,为了更清晰的了解编译器的行为,这里实现了构造/析构及拷贝构造、赋值操作、右值构造函数,如下

class HeavyObject
{
public:HeavyObject() { cout << "Constructor\n"; }~HeavyObject() { cout << "Destructor\n"; }HeavyObject(HeavyObject const&) { cout << "Copy Constructor\n"; }HeavyObject& operator=(HeavyObject const&) { cout << "Assignment Operator\n"; return *this; }HeavyObject(HeavyObject&&) { cout << "Move Constructor\n"; }
private:// many members omitted...
};

编译环境:
AppleClang 10.0.1.10010046

* 第一种使用方式

HeavyObject func()
{return HeavyObject();
}// call
HeavyObject o = func();

按照以往对C++的理解,HeavyObject类的构造析构顺序应该为

Constructor

Copy Constructor
Destructor
Destructor

但是实际运行后的输出结果却为

Constructor

Destructor

实际运行中少了一次拷贝构造和析构的开销,编译器帮助我们作了优化。

于是我反汇编了一下:

0000000100000f60 <__Z4funcv>:100000f60:    55                       push   %rbp100000f61:    48 89 e5                 mov    %rsp,%rbp100000f64:    48 83 ec 10              sub    $0x10,%rsp100000f68:    48 89 f8                 mov    %rdi,%rax100000f6b:    48 89 45 f8              mov    %rax,-0x8(%rbp)100000f6f:    e8 0c 00 00 00           callq  100000f80 <__ZN11HeavyObjectC1Ev>100000f74:    48 8b 45 f8              mov    -0x8(%rbp),%rax100000f78:    48 83 c4 10              add    $0x10,%rsp100000f7c:    5d                       pop    %rbp100000f7d:    c3                       retq   100000f7e:    66 90                    xchg   %ax,%ax

上述汇编代码中的__Z4funcv即func()函数,__ZN11HeavyObjectC1Ev即HeavyObject::HeavyObject()。
不同编译器的C++修饰规则略有不同。

实际上这里就是先创建外部的对象,再将外部对象的地址作为参数传给函数func,类似style 2方式。

* 第二种使用方式

HeavyObject func()
{HeavyObject o;return o;
}// call
HeavyObject o = func();

运行上述调用代码的结果为

Constructor

Destructor

与第一种使用方式的结果相同,这里编译器实际做了NRVO,来看一下反汇编

0000000100000f40 <__Z4funcv>: // func()100000f40:    55                       push   %rbp100000f41:    48 89 e5                 mov    %rsp,%rbp100000f44:    48 83 ec 20              sub    $0x20,%rsp100000f48:    48 89 f8                 mov    %rdi,%rax100000f4b:    c6 45 ff 00              movb   $0x0,-0x1(%rbp)100000f4f:    48 89 7d f0              mov    %rdi,-0x10(%rbp)100000f53:    48 89 45 e8              mov    %rax,-0x18(%rbp)100000f57:    e8 24 00 00 00           callq  100000f80 <__ZN11HeavyObjectC1Ev> // HeavyObject::HeavyObject()100000f5c:    c6 45 ff 01              movb   $0x1,-0x1(%rbp)100000f60:    f6 45 ff 01              testb  $0x1,-0x1(%rbp)100000f64:    0f 85 09 00 00 00        jne    100000f73 <__Z4funcv+0x33>100000f6a:    48 8b 7d f0              mov    -0x10(%rbp),%rdi100000f6e:    e8 2d 00 00 00           callq  100000fa0 <__ZN11HeavyObjectD1Ev> // HeavyObject::~HeavyObject()100000f73:    48 8b 45 e8              mov    -0x18(%rbp),%rax100000f77:    48 83 c4 20              add    $0x20,%rsp100000f7b:    5d                       pop    %rbp100000f7c:    c3                       retq   100000f7d:    0f 1f 00                 nopl   (%rax)

从上面的汇编代码可以看到返回一个具名的本地对象时,编译器优化操作如第一种使用方式一样直接在外部对象的指针上执行构造函数,只是如果构造失败时还会再调用析构函数。

以上两种使用方式编译器所做的优化非常相近,两种方式的共同点都是返回本地的一个对象,那么当本地存在多个对象且需要根据条件选择返回某个对象时结果会是如何呢?

* 第三种使用方式

HeavyObject dummy(int index)
{HeavyObject o[2];return o[index];
}// call
HeavyObject o = dummy(1);

运行后的结果为

Constructor

Constructor
Copy Constructor
Destructor
Destructor
Destructor

从运行的结果可以看到没有做RVO优化,此时调用了拷贝构造函数。

从上述三种实现方式可以看到,如果你的函数实现功能比较单一,比如只会对一个对象进行操作并返回时,编译器会进行RVO优化;如果函数实现比较复杂,可能会涉及操作多个对象并不确定返回哪个对象时,编译器将不做RVO优化,此时函数返回时会调用类的拷贝构造函数。

但是,当只存在一个本地对象时,编译器一定会做RVO优化吗?

* 第四种使用方式

HeavyObject func()
{return std::move(HeavyObject());
}// call
HeavyObject o = func();

实际运行输出的结果是

Constructor

Move Constructor
Destructor
Destructor

上述的函数实现直接返回临时对象的右值引用,从实际的运行结果来看调用了Move构造函数,与第一种使用方式运行的结果明显不同,并不是我期望的只调用一次构造函数和析构函数,也就是说编译器没有做RVO。

* 第五种使用方式

HeavyObject func()
{HeavyObject o;return static_cast<HeavyObject&>(o);
}// call
HeavyObject o = func();

实际运行输出的结果是

Constructor

Copy Constructor
Destructor
Destructor

上述的函数实现直接返回本地对象的引用,实际运行结果仍然调用了拷贝构造函数,并不是期望的只调用一次构造和析构函数,也就是说编译器并没有做RVO。

从上述两种使用方式可以看到,当返回一个对象时且对象类型与返回类型不一致时,编译器将不做RVO。实际上C++标准文档中有如下描述:

in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv-unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value

总结

  • 两种style代码的性能可能会不一样,当你非常确定你的代码的开发环境及编译器的支持特性如RVO,以及使用者的接入环境时,建议使用style 1,否则建议使用style 2
  • RVO的编译器优化特性需要相对比较严格的限制,使用style 1时,较复杂的函数实现可能并不会如你期望的使用RVO优化

作者:lifesider

原文链接 

本文为阿里云原创内容,未经允许不得转载

 

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

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

相关文章

实时计算pv/uv Demo

简介&#xff1a; 本文由阿里巴巴高级技术专家邓小勇&#xff08;静行&#xff09;分享&#xff0c;主要用 Demo 演示如何通过实时计算 Flink 实时计算pv/uv的场景。 本文由阿里巴巴高级技术专家邓小勇&#xff08;静行&#xff09;分享&#xff0c;主要用 Demo 演示如何通过实…

《天际友盟DRP数字风险防护报告(2021年上半年)》重磅发布

今天&#xff0c;数字化正在发生&#xff0c;整个社会正在步入数字化革新。根据市场研究公司IDC的预测&#xff0c;到2023年超过50%的全球经济将由数字经济所驱动。在中国&#xff0c;2021-2024数字化转型总支出将达到1.5万亿美元&#xff0c;年均增长率超过17%。由此可见&…

android自定义图片+文字控件四种实现方法,Android自定义“图片+文字”控件四种实现方法之 二--------个人最推荐的一种...

http://blog.csdn.net/yanzi1225627/article/details/8633872第二种方法也要新建一个图片文字的xml布局文件&#xff0c;然后写一个类继承自LinearLayout。在主程序里实例化并设置相应参数。这种方式也是我最推荐的一种。第一部分&#xff1a;myimgbtn_layout.xmlandroid:layou…

Android Native crash 处理案例分享

简介&#xff1a; Android Native crash 处理案例分享 1. 背景 目前 mPaas[1] Android使用Crash SDK对闪退进行的处理&#xff0c;CrashSDK 是 Android 平台上一款功能强大的崩溃日志收集 SDK&#xff0c;有着极高的崩溃收集率和完整、全面的崩溃日志信息&#xff0c;生成的日…

Mendix:低代码与无代码的异同点与用例

投稿 | Mendix 编辑 | 宋 慧 头图 | 付费下载于 IC photo 低代码和无代码应用开发都遵循着代码抽象化原则来实现建模的可视化。但基于这两种方法构建的应用在规模和类型却有着根本性的区别。 低代码与无代码的相同之处 低代码和无代码开发平台都无需编写代码就能构建软件应用…

解读:云原生下的可观察性发展方向

简介&#xff1a; 非常有幸参加了云原生社区Meetup北京站&#xff0c;有机会和众多业内的大牛一起讨论云原生相关的技术和应用&#xff0c;本次Meetup上我和大家分享了关于云原生下的可观察性相关的议题&#xff0c;本篇文章主要是视频的文字性总结&#xff0c;欢迎大家留言讨论…

android air flash,在Adobe Flash上​​触摸滚动Android上的Air

解决方案1&#xff1a;尝试用以下替换Math.max(Math.min(maxY, _startY offsetY), minY);&#xff1a;clamp(mouseY - _startY offsetY, minY, maxY);function clamp(original:Number, low:Number, high:Number):Number {return (original > high) ? high : (original &l…

一文读懂 Serverless,将配置化思想复用到平台系统中

简介&#xff1a; 搭建一个 aPaaS 平台是需要很长时间的&#xff0c;当然也可以基于一些公有云产品的 Serverless 方案实现现有系统的灵活性与扩展性&#xff0c;从而实现针对于不同客户的定制。 写在前面 在 SaaS 领域 Salesforce 是佼佼者&#xff0c;其 CRM 的概念已经扩展…

9.9 元福利价,解锁校园满分计划

移动云开发者社区致力于为广大开发者提供技术交流和能力输出&#xff0c;是移动云开发者交流汇聚地、移动云产品首席体验官工作台、移动云技术能力布道者讲台和移动云能力输出窗口。通过移动云开发者社区&#xff0c;在帮助移动云开发者用好云、好用云的同时&#xff0c;还可以…

亲历者说 | 完整记录一年多考拉海购的云原生之路

简介&#xff1a; 考拉海购的整个云化改造是从 2019 年 10 月份开始的&#xff0c;当时的唯一目标就是短时间内快速完成迁移。在不到 4 个月的时间里&#xff0c;考拉团队唯一考虑的是如何以最快的速度完成使命&#xff0c;云原生是我们选择的最合适的一条路。 前言 考拉海购的…

android 模拟器声音设置,使用android模拟器录制声音

我试图通过创建一个android应用程序来录制声音。 这里是代码&#xff1a;使用android模拟器录制声音这是辅助类package com.recorder;import java.io.File;import java.io.IOException;import android.media.MediaRecorder;import android.os.Environment;public class AudioRe…

为了一个HTTPS,浏览器操碎了心···

作者&#xff1a;轩辕之风O来源&#xff1a;编程技术宇宙 浏览器我是一个浏览器&#xff0c;每到夜深人静的时候&#xff0c;主人就打开我开始学习。为了不让别人看到浏览记录&#xff0c;主人选择了“无痕模式”。但网络中总是有很多坏人&#xff0c;他们通过抓包截获我和服务…

深度 | 阿里云蒋江伟:什么是真正的云原生?

简介&#xff1a; 而今&#xff0c;云原生成了耳熟能详的热门词&#xff0c;似乎不提云原生就落伍了&#xff0c;加入 CNCF 也成了云厂商引以为傲的技术优势。 我们也看到各种云原生的定义&#xff0c;有来自 CNCF 的“微服务容器持续交付DevOps”&#xff0c;也有来自不同云厂…

媒体智能-淘宝直播流媒体互动实践 | D2 分享视频+文章

背景&#xff1a;今天给大家带来的分享主题是《媒体智能-淘宝直播流媒体互动实践》&#xff0c;内容分为5个部分&#xff0c;首先看看在淘宝直播的直播间里主播可以怎样给用户拜年&#xff1b;然后具体讲如何制作一个手势拜年的特效&#xff1b;接着介绍我们媒体智能整体的方案…

html的语义化面试题,前端面试题-HTML结构语义化

一、HTML语义化的背景HTML结构语义化&#xff0c;是近几年才提出来的&#xff0c;对比之前的 HTML 结构&#xff0c;大多是一堆没有语义的标签。用的最多的就是 DIVCSS&#xff0c;为了改变这种现状&#xff0c;开发者们和官方提出了 HTML结构语义化的概念&#xff0c;并且在 H…

从云网络时延看应用部署架构

简介&#xff1a; 介绍云网络时延的构成&#xff0c;并对其进行量化的分析&#xff0c;以及从云网络时延看不同应用对应的部署架构。 也简单的分析了5G时代对应用部署架构的影响和度量云网络时延的产品和工具。 在引出云网络时延这看起来比较专业的话题前&#xff0c;先看几个比…

mPaas 研发流程和线上运维介绍

简介&#xff1a; mPaas 研发流程和线上运维介绍 1. 背景 金融级移动开发平台 mPaaS[1]&#xff08;Mobile PaaS&#xff09;为 App 开发、测试、运营及运维提供云到端的一站式解决方案&#xff0c;能有效降低技术门槛、减少研发成本、提升开发效率&#xff0c;协助企业快速搭…

html翻转切换div效果,图片翻转效果

图片翻转效果* { margin: 0; padding: 0;}ul { list-style-type: none;}body { font: 14px "Microsoft Yahei"; overflow-x: hidden; background-color: #2B2B2B; }h1 { width: 900px; margin: 40px auto 100px; font: 32px "Microsoft Yahei"; text-align…

市值突破3000亿,Fortinet发布2021Q2财报

Fortinet 近日发布了截至6月30日的2021财年第二季度财报。Fortinet 第二季度营收增长强劲&#xff0c;高达 8.01 亿美元&#xff0c;同比增长 30%。 截至美国时间2021年8月12日纳斯达克证券交易市场收盘&#xff0c;Fortinet公司股价达到309.33美元&#xff0c;市值达到505.2亿…

Apache Flink 在实时金融数据湖的应用

简介&#xff1a; 本文由京东搜索算法架构团队分享&#xff0c;主要介绍 Apache Flink 在京东商品搜索排序在线学习中的应用实践 一、背景 在京东的商品搜索排序中&#xff0c;经常会遇到搜索结果多样性不足导致系统非最优解的问题。为了解决数据马太效应带来的模型商品排序多…