理解返回值优化和返回临时变量 - C++

23

请考虑这三个函数。

std::string get_a_string()
{
    return "hello";
}

std::string get_a_string1()
{
    return std::string("hello");
}

std::string get_a_string2()
{
    std::string str("hello");
    return str;
}
  1. 在这三种情况下,RVO是否会被应用?
  2. 在上面的代码中返回一个临时对象是否可行?我认为可以,因为我是通过值返回它,而不是返回任何引用。

有什么想法吗?

4个回答

17
在前两种情况下,将进行RVO优化。 RVO是一项旧功能,大多数编译器都支持它。最后一种情况被称为NRVO(命名的RVO)。这是C ++的相对较新的功能。标准允许但不要求实现NRVO(以及RVO),但是某些编译器支持它。
您可以在Scott Meyers的书More Effective C ++。35种提高程序和设计的新方法的第20条中了解有关RVO的更多信息。 这里是有关Visual C ++ 2005中NRVO的良好文章。

在VS2008中有没有关闭所有优化,包括RVO的方法?我已经关闭了所有优化,但它仍然执行RVO。这只是为了了解差异。 - Navaneeth K N
/Od 应该禁用 NRVO,但我不确定 RVO。 - Kirill V. Lyadvinsky
我非常确定RVO不适用于调试版本。 - Alexandre Bell
@Krugar:好的,已经应用了。我正在运行调试版本。 - Navaneeth K N
10
即使禁用优化,MSVC也会应用返回值优化(RVO),但它不会应用命名返回值优化(NRVO)。 - jalf
你是如何测试这个的?请在调试模式和发布模式下构建以下代码:#include <iostream>class myClass { public: myClass() {} myClass(const myClass&) { std::cout << "RVO was not applied!\n"; }
};myClass foo() { myClass bar; return bar;
}int main() { myClass baz = foo(); }
- Alexandre Bell

7

首先,按值返回临时对象是完全可以的,这也是你所做的。它会被复制,尽管原始对象将超出作用域,但副本不会,并且可以安全地由调用者使用。

其次,所有三种情况实际上都是相同的(因为在第三种情况下你不访问临时对象),编译器甚至可能为它们发出相同的代码。因此,它可以在所有三种情况下使用RVO。这完全取决于编译器。


2
所有情况都是正确的。它们都将构建一个临时对象并应用返回类型的复制构造函数。如果没有复制构造函数,代码将会失败。
在大多数编译器下,所有三种情况都会发生RVO。唯一的区别是最后一种情况,标准不强制要求。这是因为你有一个命名变量。但大多数编译器仍然足够聪明,可以对它应用RVO……命名变量声明得越晚,应用的转换越少,RVO应用于命名变量的可能性就越大。
顺便说一句,返回引用当然是可能的,就像您可能在其他代码中看到的那样。你不能返回一个局部对象的引用。
std::string& get_a_string2()
{
    std::string str("hello");
    return str; //error!
}

如您所知,这会产生编译时错误。然而,

std::string& get_a_string2(std::string& str)
{
    // do something to str
    return str; //OK
}

这将完美地运行。在这种情况下,没有涉及构造或复制构造。只需函数返回其参数的引用。


1
然而,我更喜欢将该函数设置为void并且不返回任何值,因为它只是处理参数。如果返回引用参数,其他人在查看代码时可能会感到困惑。 - Viktor Sehr

1
  1. 这取决于你的编译器 - 你指的是哪个平台?找出最好的方法是编译一个非常小的测试应用程序并检查编译器生成的 ASM。

  2. 是的,这没问题,尽管你从未提到你所关心的是什么;速度?风格?你可以将本地临时变量转换为 const 引用 - 临时变量的生命周期将延长到引用的生命周期 - 试一试,亲自体验!(Herb Sutter 在 这里 解释了这个问题)请参见文章末尾的示例。

在我看来,你几乎总是更好地相信你的编译器为你优化代码。只有极少数情况下,你需要关心这种事情(非常低级的代码是其中之一,你正在与硬件寄存器交互)。

int foo() { return 42; }

int main(int, char**)
{
    const int &iRef = foo();
    // iRef is valid at this point too!
}

4
错误的。你不能返回一个指向本地变量或在该函数中创建的临时变量的引用。一旦函数返回,它们将被销毁,引用返回值将指向垃圾。我认为你想到的是const T& t = T();,这是另一回事。 - Johannes Schaub - litb
是的,那就是我想要的。我现在已经更明确地表达了。 - Thomi

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接