从函数返回STL向量 - 复制成本

时间:2012-09-26 14:48:53

标签: c++ stl stdvector

从函数返回stl向量时:

vector<int> getLargeArray() {  ...  }

回报是否是昂贵的复制操作?我记得在哪里读过矢量赋值很快 - 我应该要求调用者传递引用吗?

void getLargeArray( vector<int>& vec ) {  ...  }

2 个答案:

答案 0 :(得分:23)

假设你的函数构造并返回新数据,你应该按值返回,并尝试确保函数本身有一个返回点返回类型vector<int>的变量,或者最坏的几个返回点都返回相同的变量。

这确保您将在任何可靠的编译器上获得命名的返回值优化,从而消除潜在副本的一个(从函数中的值到返回值)。还有其他方法可以获得返回值优化,但它不是完全可预测的,因此简单的规则可以安全。

接下来,您希望将返回值中的潜在副本消除为调用者对其执行的操作。这是调用者的问题,而不是被调用者,并且基本上有三种方法可以解决这个问题:

  • 使用对该函数的调用作为vector<int>的初始化程序,在这种情况下,任何可靠的C ++编译器都会忽略该副本。
  • 使用C ++ 11,其中vector具有移动语义。
  • 在C ++ 03中,使用“swaptimization”。

也就是说,在C ++ 03中

vector<int> v;
// use v for some stuff
// ...
// now I want fresh data in v:
v = getLargeArray();

相反:

getLargeArray().swap(v);

这样可以避免v = getLargeArray()所需的复制分配(不得忽略[*])。在C ++ 11中不需要它,在那里有一个便宜的移动分配而不是昂贵的复制分配,但当然它仍然有效。

另一件需要考虑的事情是,您是否真的想要vector作为界面的一部分。您可以改为编写一个带有输出迭代器的函数模板,并将数据写入该输出迭代器。想要向量中的数据的呼叫者可以传递std::back_inserter的结果,因此想要dequelist中的数据的呼叫者也可以。事先知道数据大小的调用者甚至可以只传递一个向量迭代器(首先是resize() d)或者是一个足够大的数组的原始指针,以避免back_insert_iterator的开销。有非模板方法可以做同样的事情,但它们很可能会以这种或那种方式产生呼叫开销。如果您担心每个元素复制int的成本,那么您会担心每个元素的函数调用成本。

如果你的函数没有构造并返回新数据,而是它返回一些现有vector<int>的当前内容并且不允许更改原始内容,则你无法避免至少一个副本当你按价值返回时因此,如果该性能是一个经过验证的问题,那么您需要查看除了按值返回之外的某些API。例如,您可能提供一对可用于遍历内部数据的迭代器,一个通过索引在向量中查找值的函数,或者甚至(如果性能问题严重到需要暴露内部数据),对向量的引用。显然,在所有这些情况下,你改变了函数的含义 - 现在不是给调用者“他们自己的数据”,而是提供了别人的数据的视图,这可能会改变。

[*]当然“似乎”规则仍然适用,人们可以想象一个C ++实现,它足够聪明地意识到,因为这是一个简单的可复制类型(int)的向量,并且从那以后你没有采用任何指向任何元素的指针(我假设),然后它可以交换,结果就像“复制”一样。但我不会指望它。

答案 1 :(得分:10)

您很可能会获得return value optimization,具体取决于函数体的结构。在C ++ 11中,您也可以从移动语义中受益。 按值返回肯定有更清晰的语义,我认为它是首选的选项,除非分析证明它是昂贵的。有一篇很好的相关文章here

这是一个带有详细虚拟类的小例子,使用旧版本的GCC(4.3.4)编译时没有优化或C ++ 11支持:

#include <vector>
#include <iostream>
struct Foo
{
  Foo() { std::cout << "Foo()\n"; }
  Foo(const Foo&) { std::cout << "Foo copy\n"; }
  Foo& operator=(const Foo&) { 
    std::cout << "Foo assignment\n"; 
    return *this;
  }
};

std::vector<Foo> makeFoos()
{
  std::vector<Foo> tmp;
  tmp.push_back(Foo());
  std::cout << "returning\n";
  return tmp;
}

int main()
{
  std::vector<Foo> foos = makeFoos();
}

我的平台上的结果是所有复制都在函数返回之前发生。如果我使用C ++ 11支持进行编译,则push_back会导致移动副本而不是C ++ 03副本构造。