编译器返回值优化(Return Value Optimization,RVO)

本文详细介绍了编译器返回值优化(RVO和NRVO)的概念,提供实例说明,并讨论了其失效的几种情况,包括条件分支、全局变量、函数参数等。建议开发者了解这些规则以优化C++程序性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

编译器返回值优化

返回值优化

返回值优化 (Return Value Optimization, RVO) 是编译器一种抑制拷贝 (Copy Elision) 的优化机制,避免代码发生不必要的拷贝。特别对于返回一些局部创建的大对象来说,有助于提高性能。虽然这是编译器的行为,但是并非所有情况下,编译器都会对返回值进行优化。因此,开发者需要搞清楚何种情况,才会触发此机制。

返回值优化包括两种形式:RVO 和 NRVO (Named RVO)。

RVO:它作用于右值对象,即临时对象。例如在函数中直接创建并返回的对象。RVO 在C++11之前就有了。从c++17以及之后,RVO是编译器的标准原则。

NRVO:它作用于有名字的局部变量。例如在函数中创建的对象然后返回。NRVO是C++11标准引入的。

这两种优化技术都可以在不改变程序语义的前提下提高程序的性能,通过减少数据拷贝和临时变量的创建,可以有效地优化函数的返回值处理。

举例说明

看如下代码:

class Test {
public:
  Test() { std::cout << "Test()" << std::endl; }
  ~Test() { std::cout << "~Test() " << std::endl; }
  Test(const Test &t) { std::cout << "Test(const Test &t)" << std::endl; }
  Test &operator=(const Test &t) {
    std::cout << "Test &operator=(const Test &t)" << std::endl;
    return *this;
  }
};

Test GetTest() {
  Test t;
  return t;
}

int main() {
  Test t = GetTest();
  return 0;
}

上面的代码先不说编译器优化,单纯从代码角度分析,main从开始运行到退出main,整个过程中Test的类成员函数调用顺序应该是:

  1. 调用Test构造函数,生成对象;
  2. 调用Test拷贝构造函数,生成临时对象;
  3. 析构第1步生成的对象;
  4. 调用Test拷贝构造函数,将第2步生成的临时变量拷贝到main()函数中的局部对象t中;
  5. 调用Test析构函数,将第2步生成的临时对象释放;
  6. 调用Test析构函数,释放main()函数中的t局部对象。

好了,现在来看下运行结果:

Test()
~Test() 

看这个运行结果,傻眼了吧,和上面预测的完全不一致,这里就是返回值优化起作用了。

我们可以通过编译参数-fno-elide-constructors禁用返回值优化,看下效果:

g++ -std=c++11 -fno-elide-constructors -g ./src/test6.cc -o test6

Test()
Test(const Test &t)
~Test() 
Test(const Test &t)
~Test() 
~Test()

这次禁用返回值优化后,运行结果和上面我们预测的完全一致了。

返回值优化失效场景

编译器并非万能,在某些场景下,返回值优化也会失效。

不同条件分支,返回不同变量

Test GetTest(bool flag) {
  Test t1;
  Test t2;
  if (flag) {
    return t1;
  }
  return t2;
}

int main() {
  Test t = GetTest(true);
  return 0;
}

返回全局变量

Test g_t;
Test GetTest() { return g_t; }

int main() {
  Test t = GetTest();
  return 0;
}

直接返回函数参数

Test GetTest(Test t) {
  return t;
}

int main() {
  Test t;
  Test t1 = GetTest(t);
  return 0;
}

返回成员变量

class Test1 {
public:
  Test t;
};

Test GetTest() {
  Test1 t1;
  return t1.t;
}

int main() {
  Test t = GetTest();
  return 0;
}

赋值operator=

Test GetTest() { return Test(); }

int main() {
  Test t;
  t = GetTest();
  return 0;
}

使用std::move返回

Test GetTest() {
  Test t;
  return std::move(t);
}

int main() {
  Test t = GetTest();
  return 0;
}

运行结果:

Test()
Test(Test &&t)
~Test() 
~Test() 

场景比较多,也很难记全,所以大道至简,最合适的还是以下面的方式来写:

bool GetTest(Test* output) 

以入参的方式来操作。

### 返回值优化 (RVO) 的概念 返回值优化Return Value Optimization, RVO)是一种由编译器执行的技术,旨在减少或消除函数返回对象时不必要的拷贝操作。当一个函数返回局部创建的对象而不是指针或引用时,通常会产生临时对象并触发复制构造函数。然而,在许多情况下,这种额外的复制是没有必要的。 通过实施 RVO编译器可以在特定条件下直接将目标位置作为构建最终结果的地方,从而跳过中间步骤中的显式副本动作[^1]。 ### RVO 的工作原理 具体来说,对于那些返回新实例而非现有实体的情况,编译器可能会采取措施让被调用方直接在其外部预期接收该值的位置上建立所需的数据结构。这意味着: - 函数内部不会真正创建独立于外界存在的临时对象; - 调用者指定的目标地址成为实际存储空间所在之处; - 构造过程发生在最终目的地处,避免了后续转移带来的开销。 例如,在下面的例子中 `func()` 方法内声明了一个名为 `temp` 的变量,并将其作为返回值传递给外面的世界;但是由于启用了 RVO 机制,则实际上并没有发生任何真正的“返回”。 ```cpp A func() { A temp(69); return temp; } ``` 这里展示的是如何利用 C++ 中的语法特性来帮助实现更高效的内存管理策略[^5]。 ### 应用场景与限制条件 尽管大多数现代编译器都能够很好地识别何时适合应用此优化方法,但在某些复杂情形下仍可能存在局限性。例如,如果涉及多态行为或是虚继承关系的话,那么即使是最先进的工具也可能无法安全地省略掉所有的潜在复制活动。此外,当面对含有异常处理逻辑或者依赖于副作用表达式的语句块时,同样难以保证完全去除所有形式上的数据传输环节[^4]。 值得注意的是,虽然 NRVORVO 是两种不同类型的优化方式,但它们共同作用于提升整体效率方面有着重要意义,特别是在资源受限环境中尤为明显[^3]。 ### 编译器的支持情况 随着 C++ 标准的发展以及各版本之间的演进,有关 RVO 的支持程度也在不断变化之中。早期的标准如 C++98 并未强制规定此类特性的存在与否及其表现形态;而到了后来诸如 C++17 这样的更新则引入了一些改进措施使得更多场合下的无损转换成为了可能。不过无论如何,始终建议开发者们关注所使用的具体开发环境所提供的文档说明以获取最新信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值