RVO优化
本问对C++中RVO和NRVO进行讲解。
1、NRVO和RVO
RVO(Return Value optimization,返回值优化)和NRVO(Named Return Value Optimization,命名返回值优化)是C++编译器对局部对象的一种优化,用于消除函数返回值时产生的临时对象和拷贝的额外开销,而是在目标位置构造返回值的优化技术,用于避免不必要的临时开销,RVO在C++98时作为性能提升常见的优化手段,但那时候并未强制,首次明确允许是在C++11中,而C++17变为强制要求。
2、优化对比
#include
using namespace std;
class Test
{
public:
Test() { cout << "构造" << endl; }
Test(const Test &T)
{
cout << "拷贝构造" << endl;
// ...
}
~Test() { cout << "析构" << endl; }
};
Test getTest()
{
Test T;
return T;
}
int main()
{
Test t = getTest();
return 0;
}
2.1 未开启优化
关闭优化:
g++ -std=c++11 main.cpp -o main.exe -fno-elide-constructors
如上代码若没有开RVO优化,会输出如下内容:
构造 // getTest() 中的局部对象 T 构造
拷贝构造 // return T → 拷贝到临时对象
析构 // getTest() 中的 T 析构(函数退出)
拷贝构造 // 临时对象拷贝到 main 的 t
析构 // 临时对象析构(表达式结束)
析构 // main 的 t 析构(程序退出)
在这个执行过程中,出现了很多不必要的开销,而RVO就是用来解决该问题的。
需要注意的时,当不强行指定C++11时,输出结果可能是如下这样的:
text构造 拷贝构造 析构 析构
这是由于移动语义的干扰。
2.2 开启优化
开启RVO优化后,输出的内容如下:
构造
析构
高下立判,在编译器优化的时候,它会跳过不必要的拷贝,直接构造和析构。
3、优化原理
如上代码中,RVO优化直接在Test t = getTest()
中预留一个内存位置,来避免重复拷贝。也就是说,RVO的优化是局部对象的创建并非在函数的栈帧中,而是在main函数t的位置,这个位置预留了一个空间,等待getTest
函数中的T
被实例化(注意:预留空间并非体现初始化,不会影响代码执行顺序),实例化的位置是之前被预留的位置,从而避免不必要的拷贝。
4、RVO和NRVO的区别
RVO和NRVO都是C++编译器为了消除不必要的对象拷贝而采用的优化技术。它们的核心区别在于 优化的触发条件 和 应用场景。
4.1 NRVO
本质是返回一个函数内部的实例对象,例如现在函数中构造一个对象,再返回它。一般用来优化局部对象存在名字的情况,需要分析生命周期和代码路径,优化较为复杂,并且在多路径返回的情况下容易失效。
4.2 RVO
直接返回一个对象(对象无名字),在局部中并没有初始化的情况,这种情况下编译器更容易分析,优化确定性很高,在C++17中,它甚至是强制性的。
4.3 NRVO可能失效的地方
即使到C++23,NRVO仍是可选的优化。开发者无法完全依赖它,例如下:
#include
using namespace std;
class Test
{
public:
Test(int a) { cout << "构造" << endl; }
Test() { cout << "构造" << endl; }
Test(const Test &T)
{
cout << "拷贝构造" << endl;
// ...
}
~Test() { cout << "析构" << endl; }
};
Test getTest()
{
Test T;
return T;
}
Test createNRVO(bool flag)
{
Test a(1), b(2);
if (flag)
{
return a; // 尝试NRVO优化a到调用方内存
}
else
{
return b; // 若返回b,编译器可能无法确定目标地址
}
}
int main()
{
Test t = createNRVO(true);
return 0;
}
以上内容输出的结果是:
构造
构造
拷贝构造
析构
析构
析构
4.4 如何细分
- 优先使用RVO:
在简单场景中,直接返回临时对象(如return Test(args);
),充分利用C++17的强制优化。 - 为NRVO创造条件:
- 保持函数单一返回路径(如只在末尾返回一个具名对象)。
- 避免在返回前对对象进行复杂操作(如多分支修改)。
- 结合移动语义:
- 为类实现移动构造函数作为备选方案。
总结:本质上无论是NRVO或RVO说的都是一个东西,做的是同一件事,它们内部实现的方式和触发条件不一致而已。但是NRVO更加宽松,它是否优化并未是一个标准的东西,开发者无法完全的依赖它,而RVO是确定性的优化,在使用时,可以根据4.1和4.2标题中的的解释来酌情使用,并且没有特殊条件时,优先使用RVO。