149

在函数中返回 a 时复制了多少数据,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;
} 

?

4

8 回答 8

201

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

std::vector<X> f();

即按值返回。

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

于 2013-03-29T13:48:50.170 回答
89

你应该按价值返回。

该标准具有提高按价值退货效率的特定功能。它被称为“复制省略”,在这种情况下更具体地说是“命名返回值优化 (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 中,上面的代码确实会导致复制,虽然理论上优化器可能能够避免它,但实际上它太难了。因此myvec = f(),在 C++03 中,您应该这样编写:

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

于 2013-03-29T14:15:49.890 回答
3

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

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

于 2013-03-29T13:51:02.430 回答
2

一个常见的 pre-C++11 习惯用法是传递对正在填充的对象的引用。

然后没有向量的复制。

void f( std::vector & result )
{
  /*
    Insert elements into result
  */
} 
于 2013-03-29T13:50:37.300 回答
2

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

  1. 返回不同命名对象的不同路径
  2. 引入了 EH 状态的多个返回路径(即使在所有路径上都返回了相同的命名对象)。
  3. 返回的命名对象在内联 asm 块中引用。

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

您的示例中应该没有真正的差异。

于 2013-03-29T13:53:48.330 回答
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;
     }
}
于 2017-11-24T13:50:45.183 回答
-2

尽管“按值返回”可能很好,但它是一种可能导致错误的代码。考虑以下程序:

    #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:核心转储。
  • 问:为什么编译器没有捕捉到错误?答:因为程序在句法上是正确的,尽管不是语义上的。
  • 问:如果修改 vecFunc() 以返回引用会发生什么?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 的需要,那么您就领先了。

于 2017-09-29T03:17:21.447 回答
-2
   vector<string> func1() const
   {
      vector<string> parts;
      return vector<string>(parts.begin(),parts.end()) ;
   } 

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

于 2019-07-22T11:10:54.753 回答