16

What is the best way (performance-wise) of returning stl containers from a function? The container returned would usually contain several thousands of items.

Method 1:

typedef std::list<Item> ItemContainer;

ItemContainer CreateManyItems() {
    ItemContainer result;

    // fill the 'result' ...

    return result;
}

ItemContainer a = CreateManyItems();

Method 2:

void CreateManyItems(ItemContainer &output) {
    ItemContainer result;

    // fill the 'result' ...

    output.swap(result);
} 

ItemContainer a;
CreateManyItems(a);

Method 3:

void std::auto_ptr<ItemContainer> CreateManyItems() {
    std::auto_ptr<ItemContainer> result(new ItemContainer);

    // fill the 'result' ...

    return result;
}

std::auto_ptr<ItemContainer> a = CreateManyItems();

Or is there any better way?

Juraj Blaho
  • 13,301
  • 7
  • 50
  • 96
  • Performance-wise ? Don't use a `list`... – Matthieu M. May 13 '11 at 14:49
  • 2
    @Matthieu M.: List is just an example. – Juraj Blaho May 13 '11 at 15:08
  • @Juraj: it is an interesting example though. The first "data structure" so to speak that is taught in Computer Science courses are lists (singly-linked and doubly-linked) because an array is just so dumb it does not warrant a mention. But as a result most students "default" choice when it comes to a simple container is `list`, because it was the first they were taught! – Matthieu M. May 13 '11 at 15:26
  • @Juraj: the second point of interest is about the cost of the move constructor. For copy elision, all containers behave the same. For the move constructor though, this is not so, as the cost of the move is proportional (more or less) to the `sizeof(Container)`. Therefore containers with little internal size (`list` or `vector`) that allocate most of their contents on the heap are cheap, whether a dumb `array` would be fully copied in a Move. – Matthieu M. May 13 '11 at 15:28
  • 3
    @Matthieu M. : My first C++ container was std::vector. I used it by default for everything. Now I try to use a container that I think is most appropriate and easy to use in the given situation. List has a nice property that iterators are not invalidated by insertion. – Juraj Blaho May 16 '11 at 09:40
  • @Juraj: exact, that is its sole advantage over others, but it's definitely one that can be handy. – Matthieu M. May 16 '11 at 12:15

8 Answers8

11

None: if you just want to fill std::list with items, then you can use std::fill or std::fill_n or a combination of standard library functions.

It's not clear how exactly you want to fill your list, so I can't comment on your code precisely. If possible, use the standard library. If you cannot, then go for Method 1, and the compiler may optimize away the return value in your code eliding the unnecessary copies, as most compilers implement RVO.

See these articles on copy elision and return value optimization (RVO):

Related questions:

An article by Dave Abrahams:


I would still emphasize this: have you seen all the generic functions provided by <algorithm> header? If not, then I would suggest you to first look into them and see if any of them (or a combination of them) can do what you want to do in your code.

If you want to create and fill the list, then you can use std::generate() or std::generate_n function.

jjv-liu
  • 5
  • 3
Nawaz
  • 353,942
  • 115
  • 666
  • 851
7

I usually use method 4 (almost identical to method 2):

void fill(ItemContainer& result) {
    // fill the 'result'
}

ItemContainer a;
fill(a);
Tamás
  • 47,239
  • 12
  • 105
  • 124
4

I would use Method 1 and hope that the compiler optimizes away the copy of the return value.

Named Return Value Optimization

Bo Persson
  • 90,663
  • 31
  • 146
  • 203
2

Method 1 is fine. It's called copy ellision, and you will find it automatically applied to transform Method 1 into Method 2, basically, but it's way less ugly to maintain.

A linked list? If you're even vaguely performance-orientated, use a std::vector.

Puppy
  • 144,682
  • 38
  • 256
  • 465
  • 1
    Linked lists offer better performance than vectors... if insertion is the prominent use case. – xtofl May 13 '11 at 11:16
  • 1
    @xtofl: You have to insert into the middle or beginning a hell of a lot of times in a list with a very large number of elements to make the difference. – Puppy May 13 '11 at 11:30
  • again: depending on the context. Copy constructions due to shifting the upper half of the vector contents may incur a huge cost. What I mean to say is: don't choose for `vector` gratuitously - most of the times it fits, indeed. Some of the times, it doesn't. – xtofl May 13 '11 at 11:50
2

Method 1 can benefit from copy elision, but the following very similar code cannot:

ItemContainer a = CreateManyItems();
// do some stuff with a
a = CreateManyItems();
// do some different stuff with a

This requires C++0x move semantics in order to efficiently avoid copying the container. When you design your function, you don't know how clients are going to want to use it, so relying on copy elision in this way might catch someone out with an unpleasant performance hit. Their fix in C++03 would be as follows, and they should be alert to situations where they need it:

ItemContainer a = CreateManyItems();
// do some stuff with a
CreateManyItems().swap(a);

Since this is basically the same as Method 2, you could offer both 1 and 2, as overloads, which hints to the caller that they should think about a potential performance trap.

Provided that the Items in the collection don't refer to each other, my preferred form of the API is as follows, but it depends what kind of interface you're exposing - because it's a template the implementation must be available when the caller is compiled (unless you can predict in advance the types it will be used with and extern those specializations):

template <typename OutputIterator>
void CreateManyItems(OutputIterator out) {
     *out++ = foo; // for each item "foo"
}

ItemContainer a;
CreateManyItems(std::back_inserter(a));
// do some stuff with a
a.clear();
CreateManyItems(std::back_inserter(a));

Now, if the caller would prefer to have the items in a deque, because they want random access, then they just need to tweak their code:

std::deque<Item> a;
CreateManyItems(std::back_inserter(a));

Or if they don't need them in a collection at all, they could write a streaming algorithm that does its work in their output iterator, and save a lot of memory.

You could instead write an iterator class that generates the Items one after another, but that tends to be a bit more fiddly because the control flow is "inside out", and doesn't necessarily make the callers code look any nicer (witness istream_iterator).

Steve Jessop
  • 273,490
  • 39
  • 460
  • 699
  • And what about `a.swap(CreateManyItems())`? Will that work either? – Juraj Blaho May 13 '11 at 13:03
  • @Juraj: no, because `swap` takes a non-const reference parameter, and you can't bind that to a temporary. But you can call non-const member functions on a temporary. Again, C++0x has something to say about this, I assume `std::list` has an rvalue swap and then that would work your way around. – Steve Jessop May 13 '11 at 13:04
1

Of these, number three has probably the best performance. Perhaps slightly better, and more flexible, would be a variation of number 2 without the swap -- just filling the container directly. Of course, that wouldn't be exception-safe.

Ernest Friedman-Hill
  • 80,601
  • 10
  • 150
  • 186
0

There is a much better way and I'm surprised none of the answers has pointed this out. You basically use output iterator. This makes your function independent of container and much more flexible.

template<typename OutIter>
void CreateManyItems(OutIter it)
{
    //generate some data
    *it++ = 1;
    *it++ = 2;
    *it++ = 3;
}

Here's how you use it:

void main()
{
    //use array as output container
    int arr[3];
    CreateManyItems(arr);

    //use vector as output container
    std::vector<float> v;
    CreateManyItems(std::back_inserter(v));
}
Shital Shah
  • 63,284
  • 17
  • 238
  • 185
0

Encapsulate away the container in an appropriate class using the pimpl idiom. Then pass/return that class by value.

Jonas Bötel
  • 4,452
  • 1
  • 19
  • 28