29

I've never used std::list<T> myself. I was wondering when people use it when we already have std::vector<T> which is just like arrays with contiguous memory. std::vector seems like a perfect choice when we need sequential container!

So my question is

  • When exactly do you prefer std::list over std::vector? and why exactly?
  • When do you prefer std::vector over std::list? and why?

If there is performance consideration, then please list them too with detail explanation/information.

If possible, quote some references also, to support your answer.

Nawaz
  • 327,095
  • 105
  • 629
  • 812
  • 3
    [In which scenario do I use a particular STL Container?](http://stackoverflow.com/questions/471432/) – Wok Feb 20 '11 at 13:53

11 Answers11

30

So my question is: when exactly do you prefer std::list over std::vector?

When I need a sequential container in a performance-sensitive area and profiling shows std::list is faster.

So far, this has never happened to me.

(I might be tempted to try std::list first when I would have to store very big objects with lots of insertion/removal in the middle. However, in practice, I've never come across such a use-case.)

sbi
  • 204,536
  • 44
  • 236
  • 426
  • 4
    +1 "*When profiling shows `std::list` is fast*" This is the best answer so far. Maths and BigO notation shucks: **Profile your code and be aware of how the machine works**, not only *theoretical properties* of algorithms/data structures. – Manu343726 Mar 08 '14 at 11:14
15

Lists are better for inserting or deleting anywhere in the middle, vectors are better for inserting at the end.

Vectors are also better for accessing elements.

This is an artefact of the way they're implemented.

So, if a collection changes very little (compared to accesses) or the changes are concentrated at the end, I'd use a vector.

If the number of changes is substantial (compared to accesses) and they're not at the ends, I'd use a list.

By way of example, reading in a collection at program startup and hardly ever changing it (or if the changes are onlt adding to the end), this would be a good candidate for a vector.

On the other hand, a phone book application for a particularly popular and fickle rock star, I'd be looking towards a list. Actually I'd be looking toward a database connection but that was the best example I could come up with at short notice :-)

As to references, the latest C++0x draft states in part (23.3.4, lists):

A list is a sequence container that supports bidirectional iterators and allows constant time insert and erase operations anywhere within the sequence, with storage management handled automatically. Unlike vectors and deques, fast random access to list elements is not supported.

Section 23.3.5 (on vectors):

A vector is a sequence container that supports random access iterators. In addition, it supports (amortized) constant time insert and erase operations at the end; insert and erase in the middle take linear time.

paxdiablo
  • 772,407
  • 210
  • 1,477
  • 1,841
  • 10
    That's all right in theory. In practice, however, `std::vector` __is usually faster__ even when, in theory, `std::list` should perform better. – sbi Feb 20 '11 at 12:37
  • @sbi: See this is confusing. It makes me feel that `list` and `vector` are **almost** same. Just because of this minor difference we've two different containers. – Nawaz Feb 20 '11 at 12:42
  • 4
    @Nawaz: `std::vector` being faster in practice is an artifact of current processor designs. The STL was thought up in the early/middle 90s, and it might not have been as obvious back then. Also, both are traditional data structures any container lib should provide for. Finally, there's always the use-case of big objects. That I have never run into this doesn't mean it doesn't exist. – sbi Feb 20 '11 at 12:51
  • 1
    I quite disagree with "vectors are better for inserting at the end" [see my answer below]( http://stackoverflow.com/a/22975070/321013) – Martin Ba Apr 09 '14 at 22:24
11

There are a few trade-offs to be considered when choosing between std::list and std::vector.

Also std::list is not about contiguous memory, it can be quite useful if you can't afford iterator invalidation or if you need amortized constant time insertion in the begin/middle/end.

Roberto Caboni
  • 6,078
  • 10
  • 19
  • 34
Leandro T. C. Melo
  • 3,852
  • 19
  • 22
8

The only (few) times I preferred std::list is due to the list::splice member function. If you are shuffling around subranges within a list or between lists this operation can be significantly faster than using std::vector.

Blastfurnace
  • 17,441
  • 40
  • 50
  • 66
4

I don't need to repeat the basics, but what I learned the hard way is that if insertion performance is relevant and you have "large" objects, then you should really consider a std::list, even if you're only inserting at the end. (Well, a list or possibly a vector of smart pointer / ptr_vector.)

We had some use cases where we had to build up collections of a priori unknown size of structs of multiple small std::string and using a std::vector totally killed insertion performance and added a non-neglible memory overhead.

The problem with std::vector in the unknown count insert scenario is:

  • Since the vector will always over allocate, you pay 50% space overhead worst case for typical implementations (A single vector of string, where an object has, e.g. 24 bytes (MSVC), given meagre 100,000 elements, could have a space overhead of 2MB. And it will multiply for larger objects with more string or other biggish members.)
  • Every time the vector has to reallocate, it has to copy all the objects around, and that ain't cheap if you have anything slighly complex. Obviously, move-semantics will help, but if the objects themselves are large, the repeated copy may still be relevant. It doesn't matter if the average amortized insertion time (at the end) is theoretically constant, if you copy all your objects muliple times during building up of the vector. You may notice this peformance hit.
  • Objects get large really quick: A struct of only two empty std::string already has non-moveable 48 bytes on MSVC.

This is not to bash std::vector, I still use it by default -- but know what to expect of your datastructures!

Martin Ba
  • 33,741
  • 27
  • 150
  • 304
2

In addition to other answers, node-base containers(list/associative containers) can provide strong exception guarantee.
Even if a container-mutating operation(for example insertion) throws an exception, all the pointers/references/iterators to the elements remain valid.
However, linear-memory(piecewise contiguous memory cell) containers can provide only basic guarantee. When an insertion throws, even if the insertion isn't actually executed, pointers/references/iterators may be invalidated (though the container itself can be destructed safely).

Ise Wisteria
  • 10,349
  • 2
  • 37
  • 26
1

Some would say that you should, arguably, never ever ever use linked list in your code again. ;)

One issue is that vectors have much better locality of reference, and the big performance gains resulting from this will then, in many cases, outweigh the advantages of more (algorithmically) efficient operations for things like delete and insert in a linked list.

(See the blog post lined above for more discussion of this specific issue, with benchmarks and so on.)

So, often a std::vector will outperform std::list, even if you are doing a certain number of operations for which a list would be more natural (e.g. arbitrary element deletion, insert at arbitrary position, or splice).

But note that, even when you do do lots of these kinds of operations, you may be better off 'hosting' your list within the contiguous buffer of a std::vector.

I discuss this in more detail in this blog post, with some diagrams and code examples: http://upcoder.com/12/vector-hosted-lists/

In the specific case when you need to delete arbitrary elements, but don't need to insert at arbitrary positions or splice, a good alternative to a full blown linked list can be to just mark entries as 'dead' and skip over these dead entries during iteration.

Thomas Young
  • 163
  • 3
  • 9
1

You use std::list when you need to frequently modify the sequence in other places than the front or back. The overhead of such operations is large in std::vector in comparision std::list.

Roberto Caboni
  • 6,078
  • 10
  • 19
  • 34
ronag
  • 43,567
  • 23
  • 113
  • 204
  • Is that not possible with `std::vector`? – Nawaz Feb 20 '11 at 12:21
  • 2
    @Nawaz: It is possible, but not in constant time. http://www.sgi.com/tech/stl/Vector.html – Will Vousden Feb 20 '11 at 12:23
  • 5
    Except that in reality, `std::vector` usually outshines `std::list` even there. (It's usually explained with vector's better data locality, which supposedly plays nicer with modern processors' caches. – sbi Feb 20 '11 at 12:24
  • 3
    The overhead actually isn't that big for a std::vector unless copying objects are really expensive. The std::list, on the other hand, allocates each list node separately, which is also an overhead. Plus it uses more memory to store pointers to nodes. – Bo Persson Feb 20 '11 at 13:27
1

Use a list when its invalidation semantics and performance characteristics match your requirements.

List can insert/erase/splice anywhere in O(1), and this doesn't invalidate any iterators. Vector is O(n) for insert/erase except at the end, and even then only for insert if size < capacity; vector can't splice. Performance is even more subtle than this, with the caching locality mentioned in another answer, for example.

Thomas Edleson
  • 2,071
  • 1
  • 16
  • 19
1

std::list is my preferred almost exclusively for but one property that vector does not share, and that's knowing my pointers into a list will always be valid. Because of this, I can use this to contain all the instances of whatever asset I need in a single area, while also loaning out references to other objects to use. Best example I could think of would be an image loader that only keeps one copy of pixels in memory, while loaning out pointers to multiple entities so they can all draw from it.

All vector has going for it is O(1) access time. While that sounds like a huge asset, in practice I have very rarely ever needed to access items in a data structure outside stepping from 'first' to 'last'

Anne Quinn
  • 10,856
  • 7
  • 40
  • 83
  • 1
    +1 for good example of list of images and multiple renderers accessing them. – Nawaz Aug 31 '11 at 05:27
  • 1
    I would agree, if it weren't for the fact that std::map/set can do exactly that, and has O(log(n)) access instead of O(n). – Mooing Duck Sep 14 '11 at 17:14
  • @MooingDuck `map` and `set` have O(log(n)) for _searching_ elements, then performing operations (insertion/delete) costs extra to maintain the _Red-Black Tree_ structure. For complete iteration from start to finish it should be identical to `list`. Also, `map` and `set` are predicated on elements being sorted but that is not always desirable in certain problems. If simulating a UTM for example, the tape must have the ability to expand from either end and the arrangement of nodes must be locked, even if the contents can change, and any changes are sequential and never require random-access. – Benjamin R May 11 '15 at 06:04
0

You should use a list when you're doing a lot of deletions / insertions.

Vector can be used if the total size of elements does not change a lot, and if you do some swapping.

Intrepidd
  • 16,630
  • 5
  • 51
  • 63