什么是RVO

RVO是Return Value Optimization的缩写,是一种编译器提供的优化技术。了解这种编译技术的工作原理,可以帮助我们写出更加简洁和高效的代码。Wiki上对于ROV解释的原文是:

Important

In the context of the C++ programming language, return value optimization (RVO) is a compiler optimization that involves eliminating the temporary object created to hold a function's return value.

在gnu的编译器中,我们可以通过-fno-elide-constructors选项来控制RVO的开关。不过,c++17的copy elision使得编译器中的这个选项是不起作用的,因为RVO默认在C++17后是打开的。所以要使关闭RVO(测试目的),我们还需要在编译时加上--std=c++14

Untiled.png

RVO示例代码

#include <iostream>
#include <vector>

using namespace std;
static int counter; // counter to identify instances of S

struct S {
int i{0};
int id;
S() : id{++counter} { std::cout << "default ctor " << id << "\n"; }
S(const S& s) : i{s.i}, id{++counter} {
std::cout << "copy ctor " << id << "\n";
}

S& operator=(const S& s) {
i = s.i;
std::cout << "assign " << s.id << " to " << id << "\n";
return *this;
}

~S() { std::cout << "dtor " << id << "\n"; }
};


S get_B() {
return S(); // 1. default ctor 1
} // 2. copy ctor 2; 3. dtor 1
  
int main() { S s = get_B(); } // 4. dtor 2

在main函数中,我门通过get_B()创建了一个类S的对象,显然S对象会在栈上开辟一块内存空间用来存放从get_B()获取到的内容。那么小s是如何初始化其内部成员的,拷贝?

Without RVO

我们先来看没有RVO优化的情况。下面这张图是对disable RVO后代码行为的解读。我们简化一下代码框架:

struct S{ /**/ };
S get_B() {return S();}
int main()
{
    S s = get_B();
}
//简单来说,main函数调用get_B创建了一个类型为S的变量s。

RVO

使用GCC12.2默认的编译,get_B()的汇编就简单了很多:

使用引用接收函数返回

如果将main函数中s变量改为引用,结果会是如何?

struct S{ /**/ };
S get_B() {return S();}
int main()
{
    S& s = get_B();
}

这个代码片段在编译时就会出错:

error: cannot bind non-const lvalue reference of type 'S&' to an rvalue of type 'S’

变量s是一个lvalue, get_B()是rvalue。rvalue没有固定的内存,当生命周期结束后内存就会被释放,生命周期结束的值是“不允许”被修改的。如果我们用一个non-const的引用来指向get_B()的结果,也就隐含了潜在对这个结果进行的修改,这显然不符合逻辑。

所以,我们需要用一个const型的引用来指向这个右值。在没有RVO时,汇编代码如下。

这段代码中,使用了一个const&S s的引用来接收get_B()的返回值。可以看到实际过程是在main函数的栈上申请了一块空间存放类型为S的临时变量(这个变量并没有一个明显的名字),然后将这块栈空间直接传给了get_B(),并在get_B()中调用了S构造。

使用引用后还有一个不同点,main函数只申请了一块变量空间,而如果用变量接收的话,会有两个类型为S的变量:

当然,如果使用RVO优化,下面的结果是类似的,细节上略有不同的是s1和s2的生命周期。s2会在使用完后调用析构函数,而s1则是在大括号之后。

S s1 = get_B();
const S& s2 = get_B();

reference

Return value optimization (RVO)

Return-value optimization is a compiler technique to avoid copying an object that a function returns as its value, including avoiding creation of a temporary object.
https://sigcpp.github.io/2020/06/08/return-value-optimization#9

Copy elision

In C++ computer programming, copy elision refers to a compiler optimization technique that eliminates unnecessary copying of objects.
https://en.wikipedia.org/wiki/Copy_elision#RVO