【问题标题】:Why RVO doesn't happen here?为什么 RVO 不会在这里发生?
【发布时间】:2013-03-01 10:48:10
【问题描述】:

我已经阅读了关于 RVO 的 Dave Abrahams article 以及关于 SO(14043609929372610818278)的其他一些 Q/A,但我仍然有一个问题。当我编译并运行以下代码时,我得到了这个输出:

Address of v in func    0x7fffac6df620
Address of v.data in func       0x2081010
Address of v in main    0x7fffac6df690
Address of v.data in func       0x20811b0
9

对我来说,似乎是复制了一份。如何将大对象从函数中传递出去?请注意,我想返回一个或多个对象而不为其编写显式结构。我将 GCC 4.6.3 与 -O2 一起使用。编辑:前两个答案表明我对编译器的期望过高。我添加了一个以相同方式运行的 main2,例如打印的地址不同。我想强调一下,动机是大对象的有效返回。

#include <iostream>
#include <vector>
#include <tuple>

std::tuple<std::vector<int>, double> func() {
  std::vector<int> v;
  v.reserve(100);
  for (int k=0;k!=100;k+=1)
    v.push_back(k);

  double a = 5.0;
  std::cout << "Address of v in func\t" << &v << std::endl;
  std::cout << "Address of v.data in func\t" << v.data() << std::endl;
  return make_tuple(v, a);
}

int main() {
  std::vector<int> v;
  double a;
  std::tie(v, a) = func();
  std::cout << "Address of v in main\t" << &v << std::endl;
  std::cout << "Address of v.data in func\t" << v.data() << std::endl;
  std::cout << v[9] << std::endl;
  return 0;
}


int main2() {
  auto tp = func();
  std::vector<int> & v = std::get<0>(tp);
  double & a = std::get<1>(tp);
  std::cout << "Address of v in main\t" << &v << std::endl;
  std::cout << "Address of v.data in func\t" << v.data() << std::endl;
  std::cout << v[9] << std::endl;
  return 0;
}

【问题讨论】:

    标签: c++ return-value-optimization


    【解决方案1】:

    如前所述,有两件事会阻止 RVO。该函数不返回v,而是返回一个由va 构造的元组。同样在 main 函数中,v 被赋值,而不是从返回值构造。

    为了得到你想要的,你可以直接使用元组而不需要额外的向量对象:

    #include <iostream>
    #include <vector>
    #include <tuple>
    
    std::tuple<std::vector<int>, double> func() {
      std::tuple<std::vector<int>, double> t;
      get<0>(t).reserve(100);
      for (int k=0;k!=100;k+=1)
        get<0>(t).push_back(k);
    
      get<1>(t) = 5.0;
      std::cout << "Address of v in func\t" << &get<0>(t) << std::endl;
      std::cout << "Address of v.data in func\t" << get<0>(t).data() << std::endl;
      return t;
    }
    
    int main()
    {
      std::tuple<std::vector<int>, double> t = func();
      std::cout << "Address of v in main\t" << &get<0>(t) << std::endl;
      std::cout << "Address of v.data in func\t" << get<0>(t).data() << std::endl;
      std::cout << get<0>(t)[9] << std::endl;
    
        return 0;
    }
    

    输出:

    Address of v in func    0x28fe80
    Address of v.data in func       0x962c08
    Address of v in main    0x28fe80
    Address of v.data in func       0x962c08
    9
    

    另一种优化是在构造元组时使用移动语义:

     return make_tuple(std::move(v), a);
    

    在这种情况下,至少可以避免复制向量的内部缓冲区:

    Address of v in func    0x28fdd4
    Address of v.data in func       0xa72c08
    Address of v in main    0x28fe64
    Address of v.data in func       0xa72c08
    9
    

    【讨论】:

      【解决方案2】:

      由于va 都已在main() 中声明为变量,因此没有可省略的副本。你在这里得到的是复制分配,而不是复制构造。相当于这个:

      struct Foo {};
      
      Foo foo() { return Foo(); }
      
      int main()
      {
        Foo f1;
        f1 = foo();  // no copy hence f1 is distinct from object returned
        Foo f2 = foo(); // We can get RVO here, returned object can be f2.
      }
      

      【讨论】:

      • 我添加了第二个示例,它应该与您的代码等效。但是,它仍然不使用 RVO。我在这里错过了什么吗?
      【解决方案3】:

      RVO 很可能在这里发生,但您给出的代码中复制省略的唯一机会是将make_tuple(v, a) 的返回值复制到func() 的返回值中。

      无论是否完成,std::vectordouble 仍将被复制。您只是将func() 的结果分配给main 中的va。复制省略(和 RVO)仅适用于复制/移动构造,不适用于赋值。

      当您在main 中执行&amp;v 时,您将获得main 第一行中定义的v 对象的地址。当然这和func中定义的v对象不同。

      【讨论】:

        【解决方案4】:

        在您的第一个示例中,数据被复制到作业中:

        int main() {
          std::vector<int> v;
          double a;
          std::tie(v, a) = func();
        

        在第二个示例中,创建元组时仍会复制数据。这个修改后的例子表明 RVO 确实发生了:

        #include <iostream>
        #include <vector>
        #include <tuple>
        
        std::tuple<std::vector<int>, double> func() {
          std::vector<int> v;
          v.reserve(100);
          for (int k=0;k!=100;k+=1)
            v.push_back(k);
        
          double a = 5.0;
        
          const auto ret = make_tuple(v, a);
          const auto &v1 = std::get<0>(ret);
        
          std::cout << "Address of v in func\t" << &v1 << std::endl;
          std::cout << "Address of v.data in func\t" << v1.data() << std::endl;
        
          return ret;
        }
        
        int main() {
          auto tp = func();
          std::vector<int> & v = std::get<0>(tp);
          double & a = std::get<1>(tp);
          std::cout << "Address of v in main\t" << &v << std::endl;
          std::cout << "Address of v.data in func\t" << v.data() << std::endl;
          std::cout << v[9] << std::endl;
        
          (void)a;
        }
        

        【讨论】:

          【解决方案5】:

          感谢您的回答。我发现 Timo 的 answer 最有帮助。这就是我如何根据自己的风格调整答案。请注意 funcmain 中的重复样板。当然,如果有人知道如何摆脱它,那就太好了!

          #include <iostream>
          #include <vector>
          #include <tuple>
          
          std::tuple<std::vector<int>, double> func() {
            std::tuple<std::vector<int>, double> tp;
            std::vector<int> & v = std::get<0>(tp);
            double & a = std::get<1>(tp);
          
            v.reserve(100);
            for (int k=0;k!=100;k+=1)
              v.push_back(k);
          
            a = 5.0;
            std::cout << "Address of v in func\t" << &v << std::endl;
            std::cout << "Address of v.data in func\t" << v.data() << std::endl;
            return tp;
          }
          
          int main() {
            std::tuple<std::vector<int>, double> tp = func();
            std::vector<int> & v = std::get<0>(tp);
            double & a = std::get<1>(tp);
          
            std::cout << "Address of v in main\t" << &v << std::endl;
            std::cout << "Address of v.data in func\t" << v.data() << std::endl;
            std::cout << v[9] << std::endl;
          
            (void)a;
            return 0;
          }
          

          【讨论】:

            猜你喜欢
            • 2012-01-12
            • 1970-01-01
            • 2012-08-02
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2017-08-03
            相关资源
            最近更新 更多