【问题标题】:When should RVO kick-in?RVO 应该什么时候开始?
【发布时间】:2010-01-08 06:29:58
【问题描述】:

从下面的代码中,如果发生了 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/

【问题讨论】:

  • 我是 OP 问题,他正在从函数返回的内容中获取参考。通常每个人都应该说“可怕,它要崩溃了!”但在这种情况下,他将其声明为 const 的事实将临时对象的寿命延长到范围的末尾,从而为他节省了预期的崩溃。但在任何情况下,由于那个引用,他无论如何都没有使用任何返回值。

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


【解决方案1】:

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
}

【讨论】:

    【解决方案2】:

    正确的答案是“只要编译器愿意”。标准并未强制(但允许)此类行为,并且其启动的确切条件因编译器和版本而异。

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

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

    编辑:仔细观察您的代码,您肯定误解了 RVO。因为你的参数是一个引用,所以函数的返回值不可能有相同的地址。

    【讨论】:

      【解决方案3】:

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

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

      #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;
      }
      

      跟进达里德的评论

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

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

      在我的机器上,使用 -fno-elide-constructors 进行编译会阻止 RVO,但不进行编译则允许。

      【讨论】:

      • @darid - 我已经添加到我的答案中以反驳键盘不显示 RVO 的事实。
      【解决方案4】:

      你好像误解了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;
      }
      

      【讨论】:

      • 您是否开启了优化等(项目属性 - C++ - 优化 - O2 或更高版本)?顺便说一句,RVO 对编译器完全没有约束力。
      • aaah 我明白了,从技术上讲,它类似于内联,编译器可以决定何时何地进行优化。
      • 是的,基本上 - 这是大多数编译器实现的常见优化,但标准不要求。
      猜你喜欢
      • 1970-01-01
      • 2017-08-03
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-16
      • 2016-12-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多