千家信息网

C++中的RVO是什么

发表于:2026-11-01 作者:千家信息网编辑
千家信息网最后更新 2026年11月01日,本篇内容主要讲解"C++中的RVO是什么",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"C++中的RVO是什么"吧!前言考虑存在这样一个类如HeavyObj
千家信息网最后更新 2026年11月01日C++中的RVO是什么

本篇内容主要讲解"C++中的RVO是什么",感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习"C++中的RVO是什么"吧!

前言

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

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

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

style 1只需要一行代码,而style 2需要两行代码
// style 1HeavyObject obj = func(params);// style 2HeavyObject 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();}// callHeavyObject o = func();

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

Constructor

Copy Constructor
Destructor
Destructor

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

Constructor

Destructor

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

于是我反汇编了一下:

0000000100000f60 <__Z4funcv>:   100000f60:    55                       push   %rbp   100000f61:    48 89 e5                 mov    %rsp,%rbp   100000f64:    48 83 ec 10              sub    $0x10,%rsp   100000f68:    48 89 f8                 mov    %rdi,%rax   100000f6b:    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),%rax   100000f78:    48 83 c4 10              add    $0x10,%rsp   100000f7c:    5d                       pop    %rbp   100000f7d:    c3                       retq      100000f7e:    66 90                    xchg   %ax,%ax

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

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

第二种使用方式

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

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

Constructor

Destructor

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

0000000100000f40 <__Z4funcv>: // func()   100000f40:    55                       push   %rbp   100000f41:    48 89 e5                 mov    %rsp,%rbp   100000f44:    48 83 ec 20              sub    $0x20,%rsp   100000f48:    48 89 f8                 mov    %rdi,%rax   100000f4b:    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),%rdi   100000f6e:    e8 2d 00 00 00           callq  100000fa0 <__ZN11HeavyObjectD1Ev> // HeavyObject::~HeavyObject()   100000f73:    48 8b 45 e8              mov    -0x18(%rbp),%rax   100000f77:    48 83 c4 20              add    $0x20,%rsp   100000f7b:    5d                       pop    %rbp   100000f7c:    c3                       retq      100000f7d:    0f 1f 00                 nopl   (%rax)

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

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

第三种使用方式

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

运行后的结果为

Constructor

Constructor
Copy Constructor
Destructor
Destructor
Destructor

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

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

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

第四种使用方式

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

实际运行输出的结果是

Constructor

Move Constructor
Destructor
Destructor

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

第五种使用方式

HeavyObject func(){    HeavyObject o;    return static_cast(o);}// callHeavyObject o = func();

实际运行输出的结果是

Constructor

Copy Constructor
Destructor
Destructor

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

到此,相信大家对"C++中的RVO是什么"有了更深的了解,不妨来实际操作一番吧!这里是网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!

函数 编译 方式 编译器 对象 结果 运行 实际 C++ 代码 拷贝 不同 开销 环境 支持 也就是 也就是说 多个 特性 开发 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 厦门中拓互联网络科技有限公司 河南真二互联网科技有限公司 盈实互联网科技 关于新基建和互联网科技 奇酷互联网科技公司 华中科技大学互联网 广州凡科互联网科技 互联网 科技服务平台 顺网科技2021年互联网排名 天速互联网络科技有限公司 顶级互联网科技成果有哪些 贵州叶脉互联网科技有限公司所属行业 宝灵互联网科技有限公司 互联网媒体平台算高科技产业 中国科技创新与世界互联网大会 内蒙古九一互联网科技 宇信科技在杭州互联网排第几 卡尔迅互联网科技有限公司 广西科技产业互联网是什么 互联网科技创新方面的资料 保险科技 互联网保险 百单网互联网科技有限公司 金融科技创新监管互联网金融 以房互联网科技 深圳期待互联网科技 银川新华互联网科技学校地址 开启互联网科技时代小说 知域互联网科技有限公司大骗子 互联网的黑科技实验室 互联网科技企业文化
0