C++返回值优化

16

这段代码:

#include <vector>

std::vector<float> getstdvec() {
    std::vector<float> v(4);

    v[0] = 1;
    v[1] = 2;
    v[2] = 3;
    v[3] = 4;

    return v;
}

int main() {
    std::vector<float> v(4);

    for (int i = 0; i != 1000; ++i)
    {
        v = getstdvec();
    }
}

我这里的错误理解是函数 getstdvec 不应该实际分配它返回的向量。 当我在valgrind/callgrind中运行它时,我看到有1001次调用 malloc; 一次是在主函数中初始向量声明,另外1000次是每次循环迭代时。

怎么回事?我如何才能像这样从一个函数返回一个向量(或任何其他对象),而不必每次都进行分配?

编辑:我知道我可以通过引用传递向量。 我的印象是可以(甚至更好)编写这样的函数,即使没有不必要的分配,也可以返回一个对象。


针对您的编辑:我们需要一个真实的问题示例,而不是这个极简的样本代码,以帮助提供一个非传递引用的解决方案。 - Mark B
1
@MarkB,其实很简单:我想要一个不必进行不必要的复制/分配就能返回向量的函数。我曾认为与 RVO 或 rvalue 相关的某些东西可以使这个非常简单的事情成为可能。一个简单的现实世界的例子是试图对向量 y 和 x,标量 k 进行 y=k*x 的操作。传统的按引用传递的函数将看起来像 void mult(const float& k,const vec& x,vec& y)。但显然,函数调用y=mult(k,x)mult(k,x,y) 更可取。 - Aurelius
2
RVO(返回值优化)是编译器对代码执行的一种操作。您的代码需要先执行某些可优化的操作(例如传递一个临时变量,然后将其分配回同一对象)。您可能已经查看了该代码并想过——嗯,我可以通过传递一个引用来优化它以获取getstdvec。为什么编译器不这样做呢?好吧,传递引用并不是由您的代码暗示的。您只能期望编译器优化您的代码已经执行的操作,而不能期望它优化它可以执行的操作。 - iheanyi
6个回答

28

当你调用一个函数时,对于像std::vector<T>这样的返回类型,编译器会为返回的对象提供内存空间。被调用的函数负责在此内存空间中构造返回的实例。

RVO / NRVO 现在可以允许编译器省略创建局部临时对象、从它复制构造返回值到内存槽、销毁临时对象并最终返回给调用者的过程。相反,被调用的函数直接在返回槽的内存中构造本地对象,并在函数结束时直接返回。

从调用者的角度来看,这是透明的:它为返回值提供内存,当调用的函数返回时,有一个有效的实例。调用者现在可以使用此对象,并负责在以后调用析构函数和释放内存。

这意味着 RVO / NRVO 仅适用于当您调用函数来构造新实例时,而不是当您分配它时。以下是一个可以应用RVO / NRVO 的示例:

std::vector<float> v = getstdvec();

但是您的原始代码使用循环,在每次迭代中,需要构建来自 getstdvec() 的结果,并将此临时变量分配给v。没有办法让RVO / NRVO去除这个临时变量。


3

您可以通过引用传递它...复制省略使得v = getstdvect()会直接在您的主函数中分配v,而不是通过通常与按值返回相关的复制来进行,但它不会跳过函数内的 v(4)。为了做到这一点,您需要通过引用将向量传入:

#include <vector>
void getstdvec(std::vector<float>& v){
  v.resize(4);//will only realocate if v is wrong size
  v[0] = 1; v[1] = 2; v[2] = 3; v[3] = 4;
  return v;
}
int main() {
  std::vector<float> v(4);
  for (int i=0; i!=1000;++i)
    getstdvec(v);
}

2
你在循环中执行的是拷贝赋值,而不是拷贝构造。RVO优化仅适用于从返回值构造变量,而不是对其进行赋值。
我无法完全理解你试图解决的真正问题。如果提供更多详细信息,可能可以提供一个良好的答案来解决你的根本问题。
目前,为了以这种方式从函数返回,你需要创建一个临时向量,在每次调用函数时返回它。

1
最简单的方法是将已创建的向量对象传递到函数中。
std::vector<float> getstdvec(std::vector<float> &myvec){

在这种情况下,您实际上不必返回它。
void getstdvec(std::vector<float> &myvec){

1
如何在不每次都分配内存的情况下从这样的函数中返回向量(或任何其他对象)?
您所做的是声明一个大小为4的本地向量,因此每次调用函数时,它都会分配内存。如果您的意思是您总是修改同一个向量,则可以考虑通过引用传递向量。
例如:
void getstdvec(std::vector<float>& vec)
{                                //^^
    //do something with vec
}

main函数中,你声明并分配了向量的空间。现在你需要执行以下操作:
for (int i=0; i!=1000;++i)
{        //^^^minor: Don't use magic number in the code like this, 
         //define a const instead
    getstdvec(vec);
}

1

不要使用返回值,可以使用引用:

void getstdvec(std::vector<float> &v)

可以避免临时对象的复制。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接