Thursday, February 26, 2009

[C++] Returning Vector from Function

[C++] Returning Vector from Function
Code:
std::vector foo()
{
std::vector v;
/* fill it */
return v; // (1)
}

void bar()
{
std::vector v = foo(); // (2)
v = foo(); // (3)
}
What really happens here?

First off, despite the apparent notation line (2) really is a copy constructor, and not a default constructor + assignment. This is mandated by the standard, and that's the reason why I used both examples (2) and (3).


So...
(2) call foo
(1) copy constructor
(back to 2) copy constructor again (ouch!)

(3) call foo
(1) copy constructor
(back to 3) assignment operator, which is usually implemented as a copy constructor (ouch again!) + swap

So as you can see, the problem is that returning an object will trigger a copy. Most modern compilers have an optimization called "Return Value Optimization" (RVO for short) which is most of the time able to merge the steps (1) and (back to...). But as with any compiler optimization you shouldn't really count on that...


That leaves us with at least one copy, which could easily be avoided:
Code:
void foo(std::vector& retVal)
{
std::vector v;
/* fill it */
retVal.swap(v); // (1)
}

void bar()
{
std::vector v; // (2)
foo(v); // (3)
}
Now we're left with:
(2) default constructor, quite lightweight to say the least
(3) call foo
(1) swapping both vectors, again hyper lightweight
(back to 3) nothing more



So as yabbadabbadont put it, the best way to pass big objects back from the callee to the caller obviously is to use a reference argument.
Although this doesn't really matter for small datasets, returning vectors or other containers from a function can quickly introduce huge overhead when the dataset grows (since it involves copying the whole dataset at least once).


Which leads me way off topic:
A class supporting copy / assignment should always have a swap() method too...
- the copy constructor just does its job
- the swap method quickly and safely (as in nothrow-guarantee) exchanges the contents of two objects
- the assignment operator is implemented in terms of copy + swap (after checking for self-assignment of course)

This way you end up with an exception-safe assignment operator for almost no effort, and many use cases can be optimized away thanks to the swap() method. It's a win-win...

No comments: