前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >浅谈RVO与NRVO

浅谈RVO与NRVO

作者头像
Andromeda
发布2024-07-27 10:18:33
1150
发布2024-07-27 10:18:33
举报
文章被收录于专栏:Andromeda的专栏

RVO 和 NRVO

RVO(Return Value Optimization,返回值优化)和 NRVO(Named Return Value Optimization,命名返回值优化)是编译器进行的优化技术,旨在减少函数返回值的拷贝或移动操作。它们是 C++编译器在某些情况下自动应用的优化策略。

无优化

如果没有返回值优化(RVO)或命名返回值优化(NRVO),那么一个函数返回临时对象的一般步骤如下:

  1. 在函数内部创建临时对象。
  2. 在函数返回之前,分配内存来存储函数的返回值。
  3. 将临时对象拷贝或移动到返回值的内存位置。
  4. 函数返回,将返回值传递给调用方。

下面是一个示例,演示了在没有返回值优化的情况下,函数返回临时对象的步骤:

代码语言:javascript
复制
std::string createString()
{
    std::string str = "Hello, World!";
    return str;  // 返回临时对象
}

int main()
{
    std::string result = createString();  // 接收返回值的对象
    std::cout << result << std::endl;
    return 0;
}

在上面的例子中,createString函数创建了一个临时的 std::string对象 str,然后在函数返回之前,将 str拷贝或移动到返回值的内存位置。在 main函数中,返回值被拷贝构造到名为 result的对象中。

如果没有返回值优化,这个过程将涉及临时对象的构造、拷贝或移动和析构。但是,通过返回值优化,编译器可以在函数内部直接构造目标位置的对象,避免了不必要的拷贝或移动操作,从而提高了性能。

RVO

RVO 是一种编译器优化技术,它避免了从函数返回时创建临时对象。当函数返回一个临时对象(通常是由构造函数直接初始化的匿名对象)时,RVO 允许编译器省略创建和销毁临时对象的过程,而是直接在接收对象的位置构造返回值。这样可以避免不必要的拷贝开销。例如:

代码语言:javascript
复制
std::string createString()
{
    return "Hello, World!";  // 返回一个临时对象
}

在上面的例子中,RVO 允许编译器直接在函数内部构造目标位置的 std::string对象,而不是通过拷贝构造临时对象。这样可以减少不必要的拷贝开销。

  • 当编译器确定可以进行 RVO 时,它会:
    1. 在调用者的栈帧上为返回值分配空间,而不是在被调用函数的栈帧上。
    2. 将返回值对象的地址传递给被调用的函数,这样被调用的函数就可以直接在该地址上构造对象。
    3. 允许函数直接在预分配的内存位置构造返回值,从而避免了额外的拷贝构造和析构调用。

NRVO

NRVO 与 RVO 类似,但适用于返回函数内部已命名的局部变量。编译器优化这个过程,允许在调用者的栈帧上直接构造局部变量,避免了将局部变量拷贝到返回值的过程。这样也可以避免不必要的拷贝开销。例如:

代码语言:javascript
复制
std::vector<int> createVector()
{
    std::vector<int> v{1, 2, 3, 4, 5};
    return v;  // NRVO将避免拷贝构造局部变量
}

在上面的例子中,NRVO 允许编译器直接在函数内部构造目标位置的 std::vector<int>对象,而不是通过拷贝构造局部变量。这样可以减少不必要的拷贝开销。

  • 在应用 NRVO 时,编译器会:
    1. 识别函数中将被返回的命名局部变量。
    2. 在调用者的栈帧上为该局部变量预留空间。
    3. 直接在该空间上构造局部变量,当函数返回时不需要移动或拷贝对象。

std::move 与优化技术的冲突

在返回局部变量时使用 std::move 时,将该局部变量转换为右值。这会阻止编译器对该局部变量进行优化,因为编译器无法确定该右值是否会被修改或继续使用,因此不能在原地构造返回值。

当使用 std::move 明确地将返回的对象转换为右值时,会改变编译器对该对象生命周期的理解。这是因为 std::move 表示一个意图,即表示该对象将不再被当前作用域使用,其资源可以被“移动”到另一个对象。由于 std::move 强制将对象视为右值,编译器必须假设该对象的资源(例如动态分配的内存)可能已经或即将被外部引用(例如,被移动到另一个对象)。

在这种情况下,编译器不能安全地在调用者的上下文中直接构造返回值。这是因为编译器不能确定在构造和移动操作之间对象的状态。如果编译器选择在原地构造对象,这可能违反 std::move 的语义,因为它意味着对象资源的所有权可能仍然在函数的作用域内。为了遵守 std::move 指示的移动语义,编译器将避免在调用者的上下文中直接构造对象,而是选择显式地执行移动构造或移动赋值操作。

代码语言:javascript
复制
#include <iostream>
#include <vector>

std::vector<int> createVector()
{
    std::vector<int> v{1, 2, 3, 4, 5};
    return std::move(v);  // 使用std::move返回局部变量
}

int main()
{
    std::vector<int> result = createVector();  // 接收返回值的对象
    // 使用返回值
    return 0;
}

在上述示例中,createVector 函数返回一个局部变量 v,使用 std::move 将其转换为右值。这将阻止编译器应用命名返回值优化(NRVO),使得编译器无法直接在函数内部构造目标位置的对象。因此,编译器将执行移动操作,将临时对象移动到返回值的位置,导致不必要的移动操作。

------本页内容已结束,喜欢请分享------
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • RVO 和 NRVO
    • 无优化
      • RVO
        • NRVO
          • std::move 与优化技术的冲突
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档