RVO(Return Value Optimization,返回值优化)是C 中的一种优化技术,用于避免不必要的对象拷贝,提高程序的性能和效率。NRVO(Named Return Value Optimization,命名返回值优化)是RVO的一种特殊情况,隶属于RVO范畴。
如下的代码分别是RVO和NRVO的使用示例。
代码语言:javascript复制People using_rvo()
{
return People{10,"tommy"};
}
People using_nrvo()
{
auto p = People{ 10,"tommy" };
return p;
}
RVO它通过在函数返回时,将局部对象直接构造在函数调用方的目标对象上,避免了额外的对象拷贝操作。NRVO是在函数返回时,如果函数中的局部对象被命名为返回值,并且没有其他对象被命名为返回值,编译器可以直接在调用函数内部构造返回值对象,避免了对象拷贝操作。
RVO的原理为当编译器检测到适用于RVO的情况时,在编译源代码时就会进行优化。这意味着编译器会检测适用情况,同理,RVO必定存在其不适用的场景——其使用限制,接下来会详述其使用限制。
使用限制
1. 返回值类型不能是引用类型
参考如下的示例代码
代码语言:javascript复制CPeople& using_nrvo_with_ref2()
{
auto p = CPeople{ 10,"tommy" };
return p;
}
针对于普通局部变量而言,msvc出现崩溃(崩溃于拷贝构造函数中),gcc中会出现段错误,返回局部变量的引用本就是危险的行为,当局部变量析构后会出现未定义行为,所以出现崩溃以及段错误都是理所当然的。禁止传递局部变量的引用。
针对于静态局部变量而言,msvc和gcc均会执行一次构造一次拷贝构造函数,即静态局部变量不存在RVO。
2. 返回值不能被异常处理包围
如下的示例代码中,返回值被try-catch包围,在gcc下未没有rvo,依次执行了构造-移动构造-析构,但是msvc下发生了rvo,
代码语言:javascript复制CPeople using_nrvo_with_exception()
{
try
{
auto p = CPeople{ 10,"tommy" };
return p;
}
catch (const std::exception&)
{
throw std::runtime_error("");
}
}
3. 函数中不能有其他返回值
如下的示例代码中,依据条件,会有相同类型但是不同值的返回值——即含有其他的返回值,gcc中并没有rvo,依次执行了构造-移动构造-析构,但是msvc下发生了rvo。
代码语言:javascript复制CPeople using_nrvo_with_mrv(bool flag)
{
if (flag)
{
auto p = CPeople{ 10,"tommy" };
return p;
}
else
{
auto p= CPeople{ 22,"janney" };
return p;
}
}
综合2和3来看,虽然返回值并未进行RVO,但是均执行的是移动构造,相对拷贝构造提高性能。
当然还有的书籍讲“函数返回的对象被其他对象引用”也会限制RVO,形如如下的代码。但是经过测试gcc和msvc中均进行了RVO,即未限制RVO,但是仍不排除部分版本的编译器会进行限制。
代码语言:javascript复制CPeople using_nrvo_with_ref()
{
auto p = CPeople{ 10,"tommy" };
auto& p_ref =p;
std::cout<<"p_ref "<<p_ref;
return p;
}
总结
RVO和NRVO的的核心思想是将局部对象直接构造在函数调用方的目标对象上,避免额外的对象拷贝。由于RVO(NRVO作为RVO的特例)是在编译期进行,所以具体的行为依赖于编译器,不同的编译器会有不同的行为,乃至于不同版本的编译器也会有不同的行为,为了写出通用性强的代码,请牢记可能会限制RVO的使用场景
- 返回值类型不能是引用类型
- 返回值不能被异常处理包围
- 函数中不能有其他返回值
- 函数返回的对象被其他对象引用