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

当返回函数中的 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;
}

266761 次浏览

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

std::vector<X> f();

也就是说,按值返回。

使用 C + + 11时,std::vector具有 move-semantic,这意味着函数中声明的 本地向量返回时将是 感动,在某些情况下,甚至连 move 都可以被编译器省略。

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

这样就没有矢量的复制了。

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

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

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

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

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

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

尽管“返回值”可能很好,但这种代码可能会导致错误。考虑以下方案:

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

即使使用 GNU g + + 报表选项-Wall-Wtrac-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> 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;
}
}
   vector<string> func1() const
{
vector<string> parts;
return vector<string>(parts.begin(),parts.end()) ;
}

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

Follow 代码将在没有拷贝构造函数的情况下工作:

你的日常生活:

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

结果: move _ v size 为16,由[0]填充