从函数返回stl向量时:
vector<int> getLargeArray() { ... }
回报是否是昂贵的复制操作?我记得在哪里读过矢量赋值很快 - 我应该要求调用者传递引用吗?
void getLargeArray( vector<int>& vec ) { ... }
答案 0 :(得分:23)
假设你的函数构造并返回新数据,你应该按值返回,并尝试确保函数本身有一个返回点返回类型vector<int>
的变量,或者最坏的几个返回点都返回相同的变量。
这确保您将在任何可靠的编译器上获得命名的返回值优化,从而消除潜在副本的一个(从函数中的值到返回值)。还有其他方法可以获得返回值优化,但它不是完全可预测的,因此简单的规则可以安全。
接下来,您希望将返回值中的潜在副本消除为调用者对其执行的操作。这是调用者的问题,而不是被调用者,并且基本上有三种方法可以解决这个问题:
vector<int>
的初始化程序,在这种情况下,任何可靠的C ++编译器都会忽略该副本。vector
具有移动语义。也就是说,在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
的结果,因此想要deque
或list
中的数据的呼叫者也可以。事先知道数据大小的调用者甚至可以只传递一个向量迭代器(首先是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副本构造。