C++ return value optimization
Asked Answered
M

6

17

This code:

#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();
    }
}

My incorrect understanding here is that the function getstdvec shouldn't have to actually allocate the vector that it's returning. When I run this in valgrind/callgrind, I see there are 1001 calls to malloc; 1 for the initial vector declaration in main, and 1000 for every loop iteration.

What gives? How can I return a vector (or any other object) from a function like this without having to allocate it every time?

edit: I'm aware I can just pass the vector by reference. I was under the impression that it was possible (and even preferable) to write a function like this that returns an object without incurring an unnecessary allocation.

Middle answered 18/10, 2013 at 16:3 Comment(3)
For your edit: We need a real example of the problem you're trying to solve rather than this extremely minimal sample code to help provide a non-pass-by-reference solution.Sumerlin
@MarkB, it really is just that simple: I want a function that returns a vector without having to make an unnecessary copy/allocation. I was under the impression that something relating to RVO or rvalues would make this very simple thing possible. A simple real-world example would be trying to do y=k*x for vectors y and x, scalar k. The traditional pass-by-reference function would look like void mult(const float& k,const vec& x,vec& y). But clearly a function call y = mult(k,x) is preferable to mult(k,x,y).Middle
RVO (Return value optimization) is something the compiler does to your code. Your code needs to first do something that can be optimized (pass around a temporary that gets assigned back to the same object for example). You may have looked at that code and thought - hmm, I could optimize it by passing a reference to getstdvec. Why doesn't the compiler just do that? Well, passing a reference is NOT implied by your code. You can only expect the compiler to optimize things your code does - not things that it could do.Slideaction
A
27

When you call a function, for a return type like std::vector<T> the compiler provides memory for the returned object. The called function is responsible for constructing the instance it returns in this memory slot.

The RVO/NRVO now allows the compiler to omit creating a local temporary object, copy-constructing the returned value in the memory slot from it, destructing the temporary object and finally returning to the caller. Instead, the called function simply constructs the local object in the return slot's memory directly and at the end of the function, it just returns.

From the caller's perspective, this is transparent: It provides memory for the returned value and when the function called returned, there is a valid instance. The caller may now use this object and is responsible for calling the destructor and freeing the memory later on.

This means that the RVO/NRVO only work for when you call a function to construct a new instance, not when you assign it. The following is an example of where RVO/NRVO could be applied:

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

but you original code uses a loop and in each iteration, the result from getstdvec() needs to be constructed and this temporary is assigned to v. There is no way that the RVO/NRVO could remove this.

Afoul answered 18/10, 2013 at 16:13 Comment(0)
A
3

You can pass it by reference...copy elision makes it so that v = getstdvect() allocates v (in your main) directly to the v (in your getstdvec()) and skips the copy usually associated with returning by value, but it will NOT skip the v(4) in your function. In order to do that, you need to take the vector in by reference:

#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);
}
Ac answered 18/10, 2013 at 16:8 Comment(0)
S
2

You're doing copy-assignment in your loop, not copy-construction. The RVO optimization only applies to constructing variables from a return value, not assigning to them.

I can't quite make out the real problem you're trying to solve here. With more details it might be possible to provide a good answer that addresses your underlying problem.

As it stands, to return from your function in such a way you'll need to create a temporary vector to return each time the function is called.

Sumerlin answered 18/10, 2013 at 16:9 Comment(0)
V
1

The simplest answer is to pass the already created vector object into the function.

std::vector<float> getstdvec(std::vector<float> &myvec){

In that case you don't really have to return it so

void getstdvec(std::vector<float> &myvec){
Vibraphone answered 18/10, 2013 at 16:8 Comment(0)
L
1

How can I return a vector (or any other object) from a function like this without having to allocate it every time?

In your way, you declare a local vector with size 4, so each time the function is called, it is going to allocate the memory. If you means that you always modify on the same vector, then you may consider pass the vector by reference instead.

For example:

void getstdvec(std::vector<float>& vec)
{                                //^^
    //do something with vec
}

inside main, you declare the vector and allocate space as what you did. You now do the following:

for (int i=0; i!=1000;++i)
{        //^^^minor: Don't use magic number in the code like this, 
         //define a const instead
    getstdvec(vec);
}
Laager answered 18/10, 2013 at 16:9 Comment(0)
N
1

in stead use the return value, you can use a reference:

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

Which can avoid the copy of temporary object

Niddering answered 18/10, 2013 at 16:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.