3

我已经阅读了关于 RVO 的 Dave Abrahams文章以及关于 SO(14043609、9293726 和 10818278)的其他一些问答仍然有一个问题。当我编译并运行以下代码时,我得到以下输出:

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

5 回答 5

4

如前所述,有两件事可以防止 RVO。该函数不返回v,而是一个由v和构造的元组a。同样在 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
于 2013-03-13T13:42:30.797 回答
3

由于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.
}
于 2013-03-13T13:18:18.787 回答
2

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

不管这是否完成,std::vectoranddouble仍然会被复制。您只是从func()tovain的结果进行分配main。复制省略(和 RVO)仅适用于复制/移动构造,不适用于赋值。

当你做&vin 时main,你只是得到v在第一行定义的对象地址main。当然,这与 中v定义的对象不同func

于 2013-03-13T13:24:50.180 回答
2

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

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;
}
于 2013-03-13T13:43:00.253 回答
0

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

#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;
}
于 2013-03-13T14:08:05.757 回答