从函数返回 stl 向量时:
vector<int> getLargeArray() { ... }
退货会是昂贵的复制操作吗?我记得在某处读到向量赋值很快——我应该要求调用者传递一个引用吗?
void getLargeArray( vector<int>& vec ) { ... }
假设您的函数构造并返回新数据,您应该按值返回,并尝试确保函数本身有一个返回点返回类型为 的变量vector<int>
,或者在最坏的情况下有几个返回点都返回相同的变量。
这确保您将在任何可靠的编译器上获得命名的返回值优化,从而消除了一个潜在的副本(从函数中的值到返回值的副本)。还有其他方法可以获得返回值优化,但它不是完全可预测的,所以简单的规则很安全。
接下来,您希望消除从返回值到调用者对其进行的任何操作的潜在副本。这是调用者的问题,而不是被调用者的问题,基本上有三种方法可以做到这一点:
vector<int>
,在这种情况下,任何可靠的 C++ 编译器都会忽略副本。vector
具有移动语义的 C++11。也就是说,在 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
数据的调用者然后可以传入 的结果,想要数据在 a 中的调用者也可以传入deque
or list
。事先知道数据大小的调用者甚至可以只传递一个向量迭代器(最好是resize()
d 优先)或一个指向足够大数组的原始指针,以避免back_insert_iterator
. 有做同样事情的非模板方式,但它们很可能会以一种或另一种方式产生调用开销。如果您担心复制int
每个元素的成本,那么您会担心每个元素的函数调用成本。
如果你的函数不构造和返回新数据,而是返回一些现有的当前内容vector<int>
并且不允许更改原始数据,那么当你按值返回时,你不能避免至少一个副本。因此,如果它的性能是一个已证明的问题,那么您需要查看一些 API,而不是按值返回。例如,您可以提供一对可用于遍历内部数据的迭代器,一个按索引在向量中查找值的函数,甚至(如果性能问题严重到需要暴露您的内部),对向量的引用。显然,在所有这些情况下,您都更改了函数的含义——现在不是给调用者“他们自己的数据”,而是提供其他人数据的视图,这可能会改变。
[*] 当然,“好像”规则仍然适用,并且可以想象一个足够聪明的 C++ 实现来实现这一点,因为这是一个可简单复制的类型 ( int
) 的向量,并且因为您没有使用任何指向任何元素(我假设),然后它可以交换,结果是“好像”它被复制了。但我不会指望它。
您很可能会得到返回值优化,具体取决于函数体的结构。在 C++11 中,您还可以从移动语义中受益。按值返回当然具有更清晰的语义,我认为它是首选选项,除非分析证明它的成本很高。这里有一篇很好的相关文章。
这是一个带有冗长虚拟类的小示例,在没有优化或 C++11 支持的情况下使用旧版本的 GCC (4.3.4) 编译:
#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 副本构造。