在C ++中,从函数返回一个向量还是不好的做法吗?

简短版本:在许多编程语言中返回大型对象(如向量/数组)很常见。 如果这个类有一个移动构造函数,或者C ++程序员认为它是奇怪/丑陋/可憎的,现在这种风格现在可以被C ++ 0x接受吗?

长版本:在C + + 0x这是仍然认为不好的forms?

std::vector<std::string> BuildLargeVector(); ... std::vector<std::string> v = BuildLargeVector(); 

传统版本看起来像这样:

 void BuildLargeVector(std::vector<std::string>& result); ... std::vector<std::string> v; BuildLargeVector(v); 

在较新的版本中,从BuildLargeVector返回的值是一个右值,因此v将使用std::vector的移动构造函数构造,假设(N)RVO不会发生。

甚至在C ++ 0x之前,由于(N)RVO,第一种forms往往是“高效的”。 但是,(N)RVO由编译器自行决定。 现在我们有了右值引用, 保证不会发生深度复制。

编辑 :问题实际上不是关于优化。 所显示的两种forms在现实世界的程序中都具有几乎相同的性能。 而过去,第一种forms的performance可能会有数量级的下滑。 因此,第一种forms在很长一段时间里是C ++编程中的主要代码异味。 现在不行了,我希望?

Dave Abrahams 对传递/返回值的速度进行了非常全面的分析。

简短的回答,如果你需要返回一个值然后返回一个值。 不要使用输出引用,因为编译器无论如何都这样做。 当然有警告,所以你应该阅读那篇文章。

至less海事组织,这通常是一个可怜的主意,但不是出于效率的原因。 这是一个糟糕的主意,因为有问题的函数通常应该写成一个通过迭代器产生输出的通用algorithm。 几乎所有接受或返回容器而不是在迭代器上运行的代码都应该被认为是可疑的。

不要误会我的意思:有时候传递类似集合的对象(比如string)是有意义的,但是对于所引用的例子,我会考虑传递或返回一个不好的主意。

要点是:

复制Elision和RVO可以避免“可怕的副本”(编译器不需要实现这些优化,在某些情况下,它不能被应用)

C ++ 0x RValue引用允许 保证的string/vector实现。

如果你可以放弃旧的编译器/ STL实现,自由地返回向量(并确保你自己的对象也支持它)。 如果您的代码库需要支持“较less”编译器,请坚持旧的风格。

不幸的是,这对你的界面有很大的影响。 如果C ++ 0x不是一个选项,并且需要保证,则可以在某些情况下使用引用计数或写时复制对象。 不过,他们有multithreading的缺点。

(我希望在C ++中只有一个答案是简单而直接的,没有条件)。

我仍然认为这是一个不好的做法,但值得注意的是我的团队使用MSVC 2008和GCC 4.1,所以我们没有使用最新的编译器。

以前在MSVC 2008中显示的许多热点归结为string复制。 我们有这样的代码:

 String Something::id() const { return valid() ? m_id: ""; } 

…注意我们使用了自己的stringtypes(这是必须的,因为我们提供了一个软件开发工具包,插件编写者可以使用不同的编译器,因此std :: string / std :: wstring的不兼容实现)。

我做了一个简单的改变,以响应调用图表采样分析会话显示String :: String(const String&)占用大量的时间。 类似上面例子中的方法是最大的贡献者(实际上分析会话显示内存分配和释放是最大的热点之一,string拷贝构造函数是分配的主要贡献者)。

我做的改变很简单:

 static String null_string; const String& Something::id() const { return valid() ? m_id: null_string; } 

然而,这造成了一个不同的世界! 在随后的分析器会话中,热点消失了,除此之外,我们还进行了大量彻底的unit testing,以跟踪应用程序的性能。 这些简单的变化后,各种性能testing时间显着下降。

结论:我们没有使用绝对最新的编译器,但是我们似乎仍然不能依靠编译器来优化拷贝以便可靠地返回值(至less在所有情况下都是如此)。 对于那些使用像MSVC 2010这样的新编译器来说,情况可能并非如此。我期待着我们能够使用C ++ 0x并简单地使用右值引用,而不必担心我们通过返回复杂按价值分类。

[编辑]正如Nate指出的,RVO适用于返回在函数内部创build的临时对象。 在我的情况下,没有这样的临时(除了我们构造一个空string的无效分支),因此RVO不适用。

只是为了挑剔一点:在许多编程语言中从函数返回数组并不常见。 在其中大部分中,返回数组的引用。 在C ++中,最接近的比喻是返回boost::shared_array

如果性能是一个真正的问题,你应该认识到移动语义并不总是比复制快。 例如,如果您有一个使用小string优化的string,那么对于小string,移动构造函数必须执行与常规拷贝构造函数完全相同的工作量。

事实上,自从C ++ 11以来, 复制 std::vector的代价在大多数情况下都消失了。

但是,应该记住, 构build新的向量(然后破坏它)的代价仍然存在,并且使用输出参数而不是按值返回仍然是有用的,因为当您希望重新使用向量的容量时。 这被logging为C ++核心指南的F.20中的一个例外。

比较:

 std::vector<int> BuildLargeVector(int i) { return std::vector<int>(1000000, i); } int main() { for (int i = 0; i < 100; ++i) { std::vector<int> v = BuildLargeVector(i); // [...] do smth with v } } 

附:

 void BuildLargeVector(/*out*/ std::vector<int>& v, int i) { v.assign(1000000, i); } int main() { std::vector<int> v; for (int i = 0; i < 100; ++i) { BuildLargeVector(/*out*/ v, i); // [...] do smth with v } } 

在第一个例子中,有很多不必要的dynamic分配/解除分配发生,这在第二个例子中通过使用旧的输出参数阻止已经分配的内存而被阻止。 无论这种优化是否值得,取决于分配/取消分配的相对成本与计算/变异值的成本相比较。