4

If I have a function that returns an STL container am I incurring a copy of the entire contents of the standard container?

e.g. Is this:

void Foo( std::vector< std::string >* string_list );

better than this:

std::vector< std::string > Foo();

Does it matter what's in the container? For instance would returning a container like this:

struct buzz {
    int a;
    char b;
    float c;
}

std::map< int, buzz > Foo();

be a more costly operation than this:

std::map< int, int > Foo();

Thanks, PaulH


Edit: This is with C++03. A C++0x solution is, unfortunately, not acceptable.

Edit2: I am using the Microsoft Visual Studio 2008 compiler.

PaulH
  • 7,759
  • 8
  • 66
  • 143
  • More importantly, does it matter? No. Write the code to be *clean* and *straightforward*, then when you're done you can use a profiler to see what the slow parts are. If returning from a function is taking up a large amount of time, only then is it worth it to obfuscate your code for performance. – GManNickG Feb 17 '11 at 21:25

5 Answers5

6

C++03 will probably do (named) return value optimization (google RVO and NRVO).

If that optimization is not applicable, C++0x will do move semantics.

Community
  • 1
  • 1
fredoverflow
  • 256,549
  • 94
  • 388
  • 662
3

I wasn't 100% sure, but NO (thanks to the commentators) :

#include <vector>
#include <iostream>

#define LOCAL_FUN

struct A {
    A() { std::cout << "default ctor" << std::endl; }
    A(const A &a) { std::cout << "copy ctor" << std::endl; }
};

#ifdef LOCAL_FUN
std::vector<A> *pVec = NULL;
#endif

std::vector<A> func()
{
    std::vector<A> vec;
#ifdef LOCAL_FUN
    pVec = &vec;
#endif
    vec.push_back(A());
    std::cout << "returning" << std::endl;
    return vec;
}

int main(int argc, char *argv[])
{
    std::vector<A> ret = func();
#ifdef LOCAL_FUN
    if (pVec) {
        std::cout << pVec->size();
    }
#endif
}

output (with LOCAL_FUN):

default ctor
copy ctor
returning
1

Edit: Some more playing with the code led me to some fun with the local variables (LOCAL_FUN). So a really bad compiler that does not optimize copying, can actually break this code...

ak.
  • 3,329
  • 3
  • 38
  • 50
2

Yes it will involve a copy of the container, but don't use void Foo( std::vector< std::string >* string_list );. Use void foo( vector<string>& string_list); instead.

Or just switch to C++0x and use a compiler that has already move optimizations implemented in the library.

Šimon Tóth
  • 35,456
  • 20
  • 106
  • 151
  • 4
    It may not incur a copy. In fact, it won’t in most cases. It really depends on the content of the function. – Konrad Rudolph Feb 17 '11 at 21:12
  • 1
    Why do you prefer the reference semantics? I've always liked the pass-by-pointer method for values I'm intending to modify within the function so that the user has to put that little `&` in when calling the function. Like signifying they understand my function will change the value. – PaulH Feb 17 '11 at 21:13
  • Pointers are semantically weaker which will allow the compiler to ignore clear code errors. Another huge issue is propagating const through pointers (huge pain). – Šimon Tóth Feb 17 '11 at 21:23
  • 3
    @Paul: Then your function should be better named. There's no confusion that `swap(x, y)` is going to modify `x` and `y`, for example. – GManNickG Feb 17 '11 at 21:23
0

First compiler is required to elide copy construction, if can't, then move, if can't then copy. So if you have a really bad compiler, you risk incurring an overhead of an extra copy. See this discussion for details.

Gene Bushuyev
  • 5,512
  • 20
  • 19
  • There is no _requirement_ that copies be elided; there is merely an allowance that a compile _may_ elide a copy under some circumstances. – James McNellis Feb 17 '11 at 21:17
  • Elision isn't required. It's not even required that the implementation "try" elision first. – Fred Nurk Feb 17 '11 at 21:17
  • @James McNellis, @Fred Nurk -- you might have parsed my reply incorrectly. I didn't mean to say that compiler required to elide, I just put the sequence compiler going through before deciding to copy. – Gene Bushuyev Feb 17 '11 at 21:25
  • Here is a correct wording, that shouldn't cause confusion: When you pass an rvalue by value, or return anything by value from a function, the compiler first gets the option to elide the copy. If the copy isn’t elided, but the type in question has a move constructor, the compiler is required to use the move constructor. Lastly, if there’s no move constructor, the compiler falls back to using the copy constructor. – Gene Bushuyev Feb 17 '11 at 21:28
0

It depends on the copy constructor of the container. C++ has pass by value semantics. So when you return a vector for function Foo() it will be returned using value semantics i.e. a copy constructor will be invoked to copy the value of the vector. In this case the copy constructor of std::vector creates a new container and copies the values. In case of passing a pointer to the container you would have to allocate the memory if you have not allocated it already so the pointer points to an actual container and not a null value. From a programming practice perspective this is not a good thing to do because you leave the semantics open to interpretation. Better idea would be to pass a reference to the container and let the function fill the container with the desired elements.

mihir
  • 1