在c ++中返回std :: vector的有效方法

时间:2013-03-29 13:46:25

标签: c++ return-value stdvector

在函数中返回std :: vector时复制了多少数据,将std :: vector放在free-store(在堆上)并返回指针即是多大的优化即是:

std::vector *f()
{
  std::vector *result = new std::vector();
  /*
    Insert elements into result
  */
  return result;
} 

比以下更有效率:

std::vector f()
{
  std::vector result;
  /*
    Insert elements into result
  */
  return result;
} 

9 个答案:

答案 0 :(得分:67)

在C ++ 11中,这是首选方式:

std::vector<X> f();

即,按价值返回。

使用C ++ 11,std::vector具有移动语义,这意味着在函数中声明的 local 向量将在返回时被移动甚至移动也可以由编译器省略。

答案 1 :(得分:36)

你应该按价值回归。

该标准具有提高按价值返回效率的特定功能。它被称为“复制省略”,更具体地说就是“命名返回值优化(NRVO)”。

编译器不必实现它,但是编译器再次 来实现函数内联(或者根本不执行任何优化)。但是,如果编译器没有优化,标准库的性能可能会非常差,并且所有严格的编译器都实现了内联和NRVO(以及其他优化)。

当应用NRVO时,以下代码中将不会复制:

std::vector<int> f() {
    std::vector<int> result;
    ... populate the vector ...
    return result;
}

std::vector<int> myvec = f();

但是用户可能想要这样做:

std::vector<int> myvec;
... some time later ...
myvec = f();

复制elision不会阻止复制,因为它是一个赋值而不是初始化。但是,您应该按值返回。在C ++ 11中,赋值通过不同的东西进行优化,称为“移动语义”。在C ++ 03中,上面的代码确实会产生副本,虽然理论上 但优化器可能能够避免它,但实际上它太难了。因此,在C ++ 03中你应该写下这个代替myvec = f()而不是{/ p>

std::vector<int> myvec;
... some time later ...
f().swap(myvec);

还有另一种选择,即为用户提供更灵活的界面:

template <typename OutputIterator> void f(OutputIterator it) {
    ... write elements to the iterator like this ...
    *it++ = 0;
    *it++ = 1;
}

您可以在此基础上支持现有的基于矢量的界面:

std::vector<int> f() {
    std::vector<int> result;
    f(std::back_inserter(result));
    return result;
}

如果您的现有代码使用reserve()的方式比预先固定金额更复杂,那么此可能的效率低于现有代码。但是如果您现有的代码基本上反复调用向量上的push_back,那么这个基于模板的代码应该是一样好的。

答案 2 :(得分:3)

是时候发布关于RVO的答案了,我也是......

如果按值返回一个对象,编译器通常会优化它,因此它不会被构造两次,因为在函数中构造它作为临时函数是多余的,然后复制它。这称为返回值优化:将移动创建的对象而不是复制。

答案 3 :(得分:1)

一个常见的C ++之前的习惯用法是将引用传递给正在填充的对象。

然后没有复制载体。

void f( std::vector & result )
{
  /*
    Insert elements into result
  */
} 

答案 4 :(得分:0)

如果编译器支持Named Return Value Optimizationhttp://msdn.microsoft.com/en-us/library/ms364057(v=vs.80).aspx),你可以直接返回向量提供没有:

  1. 返回不同命名对象的不同路径
  2. 多个返回路径(即使返回相同的命名对象) 引入EH州的所有路径。
  3. 返回的命名对象在内联asm块中引用。
  4. NRVO优化冗余拷贝构造函数和析构函数调用,从而提高整体性能。

    你的例子中应该没有真正的差异。

答案 5 :(得分:0)

vector<string> getseq(char * db_file)

如果你想在main()上打印它,你应该循环播放。

int main() {
     vector<string> str_vec = getseq(argv[1]);
     for(vector<string>::iterator it = str_vec.begin(); it != str_vec.end(); it++) {
         cout << *it << endl;
     }
}

答案 6 :(得分:0)

是的,按价值返回。编译器可以自动处理它。

答案 7 :(得分:0)

IAsyncCollector

答案 8 :(得分:-1)

和#34一样好;按价值回报&#34;可能是,它可能导致一个错误的代码。请考虑以下程序:

    #include <string>
    #include <vector>
    #include <iostream>
    using namespace std;
    static std::vector<std::string> strings;
    std::vector<std::string> vecFunc(void) { return strings; };
    int main(int argc, char * argv[]){
      // set up the vector of strings to hold however
      // many strings the user provides on the command line
      for(int idx=1; (idx<argc); ++idx){
         strings.push_back(argv[idx]);
      }

      // now, iterate the strings and print them using the vector function
      // as accessor
      for(std::vector<std::string>::interator idx=vecFunc().begin(); (idx!=vecFunc().end()); ++idx){
         cout << "Addr: " << idx->c_str() << std::endl;
         cout << "Val:  " << *idx << std::endl;
      }
    return 0;
    };
  • 问:执行上述操作后会发生什么?答:一个coredump。
  • 问:为什么编译器没有抓到错误?答:因为该计划是 语法上,虽然不是语义上的,但是正确的。
  • 问:如果修改vecFunc()以返回引用会发生什么?答:程序运行完成并产生预期结果。
  • 问:有什么区别?答:编译器没有 必须创建和管理匿名对象。程序员指示编译器只使用一个对象用于迭代器和端点确定,而不是像破碎的例子那样使用两个不同的对象。

即使使用GNU g ++报告选项-Wall -Wextra -Weffc ++

,上述错误程序也表示没有错误

如果你必须产生一个值,那么以下代码可以代替两次调用vecFunc():

   std::vector<std::string> lclvec(vecFunc());
   for(std::vector<std::string>::iterator idx=lclvec.begin(); (idx!=lclvec.end()); ++idx)...

上面在循环迭代期间也没有产生匿名对象,但需要一个可能的复制操作(正如一些说明的那样,在某些情况下可能会优化掉。但是引用方法保证不会产生任何副本。相信编译器将执行RVO无法替代尝试构建最有效的代码。如果您不需要编译器来执行RVO,那么您就领先于游戏。