Which STL container should I use for a FIFO?
Solution 1:
Since there are a myriad of answers, you might be confused, but to summarize:
Use a std::queue
. The reason for this is simple: it is a FIFO structure. You want FIFO, you use a std::queue
.
It makes your intent clear to anybody else, and even yourself. A std::list
or std::deque
does not. A list can insert and remove anywhere, which is not what a FIFO structure is suppose to do, and a deque
can add and remove from either end, which is also something a FIFO structure cannot do.
This is why you should use a queue
.
Now, you asked about performance. Firstly, always remember this important rule of thumb: Good code first, performance last.
The reason for this is simple: people who strive for performance before cleanliness and elegance almost always finish last. Their code becomes a slop of mush, because they've abandoned all that is good in order to really get nothing out of it.
By writing good, readable code first, most of you performance problems will solve themselves. And if later you find your performance is lacking, it's now easy to add a profiler to your nice, clean code, and find out where the problem is.
That all said, std::queue
is only an adapter. It provides the safe interface, but uses a different container on the inside. You can choose this underlying container, and this allows a good deal of flexibility.
So, which underlying container should you use? We know that std::list
and std::deque
both provide the necessary functions (push_back()
, pop_front()
, and front()
), so how do we decide?
First, understand that allocating (and deallocating) memory is not a quick thing to do, generally, because it involves going out to the OS and asking it to do something. A list
has to allocate memory every single time something is added, and deallocate it when it goes away.
A deque
, on the other hand, allocates in chunks. It will allocate less often than a list
. Think of it as a list, but each memory chunk can hold multiple nodes. (Of course, I'd suggest that you really learn how it works.)
So, with that alone a deque
should perform better, because it doesn't deal with memory as often. Mixed with the fact you're handling data of constant size, it probably won't have to allocate after the first pass through the data, whereas a list will be constantly allocating and deallocating.
A second thing to understand is cache performance. Going out to RAM is slow, so when the CPU really needs to, it makes the best out of this time by taking a chunk of memory back with it, into cache. Because a deque
allocates in memory chunks, it's likely that accessing an element in this container will cause the CPU to bring back the rest of the container as well. Now any further accesses to the deque
will be speedy, because the data is in cache.
This is unlike a list, where the data is allocated one at a time. This means that data could be spread out all over the place in memory, and cache performance will be bad.
So, considering that, a deque
should be a better choice. This is why it is the default container when using a queue
. That all said, this is still only a (very) educated guess: you'll have to profile this code, using a deque
in one test and list
in the other to really know for certain.
But remember: get the code working with a clean interface, then worry about performance.
John raises the concern that wrapping a list
or deque
will cause a performance decrease. Once again, he nor I can say for certain without profiling it ourselves, but chances are that the compiler will inline the calls that the queue
makes. That is, when you say queue.push()
, it will really just say queue.container.push_back()
, skipping the function call completely.
Once again, this is only an educated guess, but using a queue
will not degrade performance, when compared to using the underlying container raw. Like I've said before, use the queue
, because it's clean, easy to use, and safe, and if it really becomes a problem profile and test.
Solution 2:
Check out std::queue
. It wraps an underlying container type, and the default container is std::deque
.
Solution 3:
Where performance really matters, check out the Boost circular buffer library.
Solution 4:
I continually
push_back
new elements whilepop_front
ing the oldest element (about a million time).
A million is really not a big number in computing. As others have suggested, use a std::queue
as your first solution. In the unlikely event of it being too slow, identify the bottleneck using a profiler (do not guess!) and re-implement using a different container with the same interface.