RVO(Return Value Optimization,返回值优化)和 NRVO(Named Return Value Optimization,命名返回值优化)是编译器进行的优化技术,旨在减少函数返回值的拷贝或移动操作。它们是 C++编译器在某些情况下自动应用的优化策略。
如果没有返回值优化(RVO)或命名返回值优化(NRVO),那么一个函数返回临时对象的一般步骤如下:
下面是一个示例,演示了在没有返回值优化的情况下,函数返回临时对象的步骤:
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 允许编译器省略创建和销毁临时对象的过程,而是直接在接收对象的位置构造返回值。这样可以避免不必要的拷贝开销。例如:
std::string createString()
{
return "Hello, World!"; // 返回一个临时对象
}
在上面的例子中,RVO 允许编译器直接在函数内部构造目标位置的 std::string
对象,而不是通过拷贝构造临时对象。这样可以减少不必要的拷贝开销。
NRVO 与 RVO 类似,但适用于返回函数内部已命名的局部变量。编译器优化这个过程,允许在调用者的栈帧上直接构造局部变量,避免了将局部变量拷贝到返回值的过程。这样也可以避免不必要的拷贝开销。例如:
std::vector<int> createVector()
{
std::vector<int> v{1, 2, 3, 4, 5};
return v; // NRVO将避免拷贝构造局部变量
}
在上面的例子中,NRVO 允许编译器直接在函数内部构造目标位置的 std::vector<int>
对象,而不是通过拷贝构造局部变量。这样可以减少不必要的拷贝开销。
在返回局部变量时使用 std::move 时,将该局部变量转换为右值。这会阻止编译器对该局部变量进行优化,因为编译器无法确定该右值是否会被修改或继续使用,因此不能在原地构造返回值。
当使用 std::move
明确地将返回的对象转换为右值时,会改变编译器对该对象生命周期的理解。这是因为 std::move
表示一个意图,即表示该对象将不再被当前作用域使用,其资源可以被“移动”到另一个对象。由于 std::move
强制将对象视为右值,编译器必须假设该对象的资源(例如动态分配的内存)可能已经或即将被外部引用(例如,被移动到另一个对象)。
在这种情况下,编译器不能安全地在调用者的上下文中直接构造返回值。这是因为编译器不能确定在构造和移动操作之间对象的状态。如果编译器选择在原地构造对象,这可能违反 std::move
的语义,因为它意味着对象资源的所有权可能仍然在函数的作用域内。为了遵守 std::move
指示的移动语义,编译器将避免在调用者的上下文中直接构造对象,而是选择显式地执行移动构造或移动赋值操作。
#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),使得编译器无法直接在函数内部构造目标位置的对象。因此,编译器将执行移动操作,将临时对象移动到返回值的位置,导致不必要的移动操作。