【问题标题】:Returning an STL vector from a function - copy cost从函数返回 STL 向量 - 复制成本
【发布时间】:2012-09-18 05:43:26
【问题描述】:

当你从一个函数返回一个 stl 向量时:

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

退货会是昂贵的复制操作吗?我记得在某处读到向量赋值很快——我应该要求调用者传递一个引用吗?

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

【问题讨论】:

  • @Gareth 除非你在 C++11 中,从函数返回向量保证要快按标准(除了返回值优化之外,在许多情况下甚至在 C++11 之前的版本中使其变得更快,但是好的,这取决于实现和情况)。

标签: c++ stl stdvector


【解决方案1】:

假设您的函数构造并返回新数据,您应该按值返回,并尝试确保函数本身有一个返回点,该返回点返回一个类型为vector&lt;int&gt; 的变量,或者最坏的情况是多个返回点都返回同一个变量。

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

接下来,您希望消除从返回值到调用者对其进行的任何操作的潜在副本。这是调用者的问题,而不是被调用者的问题,基本上有三种方法可以做到这一点:

  • 使用对函数的调用作为 vector&lt;int&gt; 的初始化程序,在这种情况下,任何可靠的 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&lt;int&gt; 的当前内容并且不允许更改原始数据,那么当您按值返回。因此,如果它的性能是一个已证明的问题,那么您需要查看一些 API,而不是按值返回。例如,您可以提供一对可用于遍历内部数据的迭代器,一个按索引在向量中查找值的函数,甚至(如果性能问题严重到需要暴露您的内部),对向量的引用。显然,在所有这些情况下,您都改变了函数的含义——现在不是给调用者“他们自己的数据”,而是提供其他人数据的视图,这可能会改变。

[*] 当然“好像”规则仍然适用,并且可以想象一个足够聪明的 C++ 实现来实现这一点,因为这是一个可简单复制类型的向量 (int),并且因为你没有'没有获取任何指向任何元素的指针(我假设),然后它可以交换,结果是“好像”它被复制了。但我不会指望它。

【讨论】:

    【解决方案2】:

    你很有可能得到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 副本构造。

    【讨论】:

    • 其实,只有当你总是返回相同的向量时,你才有可能得到 RVO...
    • @ltjax 是的,属于“函数体的结构”。由于没有写在标准中,我不想过多阐述。
    猜你喜欢
    • 2017-04-08
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-07-18
    • 2018-12-13
    • 2023-04-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多