浅析RVO

2024-07-18 13:27:17 浏览数 (2)

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的使用场景

  • 返回值类型不能是引用类型
  • 返回值不能被异常处理包围
  • 函数中不能有其他返回值
  • 函数返回的对象被其他对象引用

0 人点赞