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

Efficient way to return a std::vector in c++

本文关键字:向量 有效 方法 std c++ 返回      更新时间:2023-10-16

在函数中返回std::vector时,复制了多少数据,将std::vector放在空闲存储区(堆上(并返回指针(即:(将是一个多大的优化

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;
} 

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

std::vector<X> f();

也就是说,按值返回。

对于C++11,std::vector具有移动语义,这意味着函数中声明的局部向量在返回时将被移动,在某些情况下,编译器甚至可以消除移动。

您应该按值返回。

该标准有一个特定的特点,即提高按价值退货的效率。它被称为"复制省略",在这种情况下更具体地说是"命名的返回值优化(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();

复制省略不会阻止此处的复制,因为它是一个赋值而不是初始化。但是,您应该仍然按值返回。在C++11中,赋值是通过一种不同的方法优化的,称为"移动语义"。在C++03中,上面的代码确实会导致复制,尽管在理论上优化器可能能够避免它,但在实践中它太难了。因此,在C++03中,您应该编写以下内容,而不是myvec = f()

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,那么这个基于模板的代码应该也一样好。

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

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

C++11之前的一个常见习惯用法是传递对要填充的对象的引用。

那么就不会复制矢量。

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

如果编译器支持命名返回值优化(http://msdn.microsoft.com/en-us/library/ms364057(v=vs.80(.aspx(,您可以直接返回矢量,前提是没有:

  1. 返回不同命名对象的不同路径
  2. 多个返回路径(即使在所有路径(,其中引入EH状态
  3. 返回的命名对象在内联asm块中被引用

NRVO优化了冗余的复制构造函数和析构函数调用,从而提高了整体性能。

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

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;
     }
}

以下代码将在没有复制构造函数的情况下工作:

你的常规:

std::vector<unsigned char> foo()
{
    std::vector<unsigned char> v;
    v.resize(16, 0);
    return std::move(v); // move the vector
}

之后,您可以使用foo例程来获取矢量,而无需复制自身:

std::vector<unsigned char>&& moved_v(foo()); // use move constructor

结果:moved_v大小为16,由[0]填充

尽管"按值返回"可能很好,但这类代码可能会导致错误。考虑以下程序:

    #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;
    };
  • Q: 执行以上操作时会发生什么?A: 堆芯转储
  • Q: 为什么编译器没有发现错误?A: 因为程序语法上,虽然不是语义上,是正确的
  • Q: 如果修改vecFunc((以返回引用,会发生什么情况?A: 程序运行到完成并产生预期结果
  • Q: 有什么区别?A: 编译器没有必须创建和管理匿名对象。程序员已经指示编译器为迭代器和端点确定只使用一个对象,而不是像坏例子那样使用两个不同的对象

上述错误程序将表明没有错误,即使使用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,那么你就领先了。

   vector<string> func1() const
   {
      vector<string> parts;
      return vector<string>(parts.begin(),parts.end()) ;
   } 

在c++11之后,这仍然是有效的,因为编译器自动使用移动而不是复制。