RVO什么时候开球?

时间:2010-01-08 06:29:59

标签: c++ return-value return-value-optimization

从下面的代码中,如果发生了RVO,我希望看到2个地址指向同一个位置,但情况并非如此(我的编译器是MS VC9.0)

#include <iostream>
#include <string>

std::string foo(std::string& s)
{
   std::cout << "address: " << (unsigned int)(&s) << std::endl;
   return s;
}

int main()
{
   std::string base = "abc";
   const std::string& s = foo(base);
   std::cout << "address: " << (unsigned int)(&s) << std::endl;
   std::cout << s << std::endl;
   return 0;
}

在什么条件下应该发生RVO?

顺便说一下,我的问题基于以下讨论:http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/

4 个答案:

答案 0 :(得分:8)

当您返回未命名的临时文件时,RVO通常适用,但如果您返回先前创建的对象,则不是

std::string foo() {
  return std::string("hello world"); // RVO
}

std::string foo() {
  std::string str("hello world");
  bar();
  return str; // Not RVO
}

std::string foo(std::string str) {
  return str; // Not RVO
}

更通用的版本是NRVO(命名返回值优化),它也适用于命名变量。

std::string foo() {
  std::string str("hello world");
  bar();
  return str; // NRVO
}

std::string foo(std::string str) {
  return str; // Not NRVO, as far as I know. The string is constructed outside the function itself, and that construction may be elided by the compiler for other reasons.
}

std::string foo(std::string str) {
  std::string ret;
  swap(ret, str);
  return ret; // NRVO. We're returning the named variable created in the function
}

答案 1 :(得分:4)

正确答案是“只要编译器满意”。标准没有强制要求(但允许)这种行为,并且它的确切条件因编译器和编译器以及版本而异。

作为一般规则,编译器比您更聪明,并且符合您的最佳利益。不要质疑它。

C ++ 0x中的右值引用是RVO的手动版本。

编辑:仔细观察您的代码,您肯定会误解RVO。因为您的参数是引用,所以函数的返回值无法具有相同的地址。

答案 2 :(得分:2)

我不知道完整的条件,但我相信你返回一个参数而不是在函数中创建的实例这一事实导致了你的例子中的问题。

对我来说,以下两个地址都显示相同的地址:

#include <iostream>
#include <string>

std::string foo()
{
   std::string s("rvo!");
   std::cout << "address: " << (void *)(&s) << std::endl;
   return s;
}

int main()
{
   const std::string s = foo();
   std::cout << "address: " << (void *)(&s) << std::endl;
   std::cout << s << std::endl;
   return 0;
}

跟进darid的评论

它使用的codepad about page文档是C ++的-fno-elide-constructors。该选项的文档构成了g ++手册页状态:

  

C ++标准允许实现省略创建              临时的,仅用于初始化的另一个对象              相同的类型。指定此选项会禁用该优化,并且              迫使G ++在所有情况下调用复制构造函数。

在我的机器上,使用-fno-elide-constructors进行编译可以防止RVO,但是在不允许的情况下进行编译。

答案 3 :(得分:1)

你似乎误解了RVO,试试这个例子(实际上是NRVO)

std::string foo(const char* const s)
{
    std::string out(s);
    std::cout << "address: " << (void*)(&out) << std::endl;
    return out;
}

int main()
{
   std::string s = foo("abc");
   std::cout << "address: " << (void*)(&s) << std::endl;
}