std::queue
std::queue
container.A queue is a data structure that replicates the properties of a real-life queue. They are ubiquitous in programming, with many use cases that we’ll cover soon.
The defining characteristic of a queue is that objects leave in the same order they were added. Because of this, a queue is sometimes described as a first-in, first-out (FIFO)Â container.
We can visualize a queue as a sequential container, where objects are only added at the back of the queue, and only removed from the front.
Adding an object to a queue is often referred to as pushing it, or enqueuing it. Removing an object from the front of a queue is referred to as popping it, or dequeuing it.
Queues are everywhere in programming. Their use cases broadly fall into three categories - simulation, asynchronous communication, and decoupling.
The most obvious use for a queue data structure is to simulate an actual, real-world queue. For example, if we’re making an online service, we may find ourselves having a surge of popularity that overloads our infrastructure.
One way to prepare for this is to build a queue system, where users need to wait until we have enough capacity to let them in.
Sometimes, our systems communicating with each other through simple function calls can present issues. For example, imagine we’re running a popular video hosting site. Users can upload their videos, which we compress, and then release to the public.
Compressing videos can take a long time. We don’t want users waiting until that completes before we tell them their upload was successful. So, we can make these processes asynchronous, with the help of a queue.
When users upload their video, rather than compressing it within the same call stack, we can simply add the video to a queue, and consider the "uploading" part of the process complete. We can tell users their upload was successful.
Our compression system can run as a separate step, grabbing videos from the queue and compressing them without holding any other process back.
The third use of a queue is more concealed from users, as it’s a way to design our systems to make them more modular. When we’re building a complex project, like a large game, we will have dozens of systems.
All our systems need an organized way to communicate with each other, and having an event queue is a common approach to take. We have a dedicated lesson on event queues, and how to implement them later in the course.
std::queue
The C++ standard library has an implementation of a queue ready for us to use. It’s available as part of the <queue>
header. It is a template class that requires us to specify the type of object our queue will contain. In this example, we create a queue of integers:
#include <queue>
int main() {
std::queue<int> Numbers;
}
We can also construct a queue with initial values by copying or moving another queue:
#include <queue>
int main() {
std::queue<int> Numbers;
std::queue<int> Copy{Numbers};
std::queue<int> Move{std::move(Numbers)};
}
We cover copy semantics, move semantics, and std::move
in more detail here:
When initializing a std::queue
with values, we can, we can omit the template parameter. This will let the compiler infer it, using Class Template Argument Deduction (CTAD). Below, the compiler will deduce both Copy
and Move
should have a type of std::queue<int>
:
#include <queue>
int main() {
std::queue<int> Numbers;
std::queue Copy{Numbers};
std::queue Move{std::move(Numbers)};
}
We can also construct a queue directly from certain types of containers, such as a std::list
:
#include <queue>
#include <list>
int main() {
std::list Nums{1, 2, 3};
std::queue NumsQueue{Nums};
}
There is a little more nuance here on what type of containers we can use, and the implications on our queue. We cover this later in the lesson, in the section on container adapters.
The queue class does not have a constructor that lets us provide a list of initial values. However, we can initialize a queue by passing an iterator pair denoting the start and end of a range of initial values.
We introduced iterators in a dedicated lesson earlier in the course:
Note, the iterator-based constructor for std::queue
is a C++23 addition, so will not yet be supported by all compilers:
#include <queue>
#include <vector>
int main() {
std::vector<int> Source{1, 2, 3};
std::queue<int> Numbers{
Source.begin(), Source.end()};
}
Again, we can use class template argument deduction:
#include <queue>
#include <vector>
int main() {
std::vector<int> Source{1, 2, 3};
std::queue Numbers{
Source.begin(), Source.end()};
}
Similar to most other sequential containers in the standard library, we have two ways to add objects to our queue.
emplace()
The emplace()
method will construct our object directly into the queue. If the object we want to add doesn’t already exist, this is the preferred method for performance reasons. Constructing an object in place tends to be faster than constructing it elsewhere, and then moving it into the container.
The emplace()
method passes any arguments along to the constructor of the type stored in our queue:
#include <queue>
#include <string>
class Character {
public:
Character(std::string Name) : Name{Name} {}
std::string Name;
};
int main() {
std::queue<Character> Q;
Q.emplace("Legolas");
}
push()
If our object already exists, we can use the queue’s push()
method to copy or move it into our container:
#include <queue>
#include <string>
class Character {/*...*/}
int main() {
Character Legolas{"Legolas"};
Character Gimli{"Gimli"};
std::queue<Character> Q;
// Copy
Q.push(Legolas);
// Move
Q.push(std::move(Gimli));
}
Queues only provide easy access to two of their objects - the object at the front of the queue, and the object at the back.
front()
The queue’s front
method returns a reference to the object at the front of our queue. By definition of a queue, the object at the front is the object that has been in the queue the longest.
#include <iostream>
#include <queue>
#include <string>
class Character {/*...*/}
int main() {
std::queue<Character> Q;
Q.emplace("Legolas");
Q.emplace("Gimli");
Q.emplace("Gandalf");
std::cout << Q.front().Name
<< " is at the front of the queue";
}
Legolas is at the front of the queue
back()
We can retrieve a reference to the object at the end of our queue using the back
method. By definition of a queue, the back of the queue is the object that was most recently added:
#include <iostream>
#include <queue>
#include <string>
class Character {/*...*/}
int main() {
std::queue<Character> Q;
Q.emplace("Legolas");
std::cout
<< "The last character in the queue is "
<< Q.back().Name;
}
The last character in the queue is Legolas
We can’t access other arbitrary elements of the queue. By design, queues are more restrictive than more general containers like std::vector
.
To preserve the first-in-first-out rule of queues, we can only remove objects from the front of the queue. We do this using the pop()
 method:
#include <iostream>
#include <queue>
#include <string>
class Character {/*...*/}
int main() {
std::queue<Character> Q;
Q.emplace("Legolas");
Q.emplace("Gimli");
Q.emplace("Gandalf");
std::cout
<< Q.front().Name
<< " is at the front of the queue\n";
Q.pop();
std::cout << Q.front().Name
<< " is now at the front";
}
Legolas is at the front of the queue
Gimli is now at the front
The queue
class has two methods to determine how many elements it contains:
size()
The size method returns a size_type
integer, telling us how many objects are in the queue:
#include <iostream>
#include <queue>
#include <string>
class Character {/*...*/}
int main() {
std::queue<Character> Q;
Q.emplace("Legolas");
Q.emplace("Gimli");
Q.emplace("Gandalf");
std::cout << Q.size()
<< " objects are in the queue\n";
Q.pop();
std::cout << Q.size()
<< " objects are now in the queue";
}
3 objects are in the queue
2 objects are now in the queue
empty()
Where we want to compare the size()
of the queue to 0
, we can use the more explicit empty()
 method:
#include <iostream>
#include <queue>
#include <string>
class Character {/*...*/}
int main() {
std::queue<Character> Q;
Q.emplace("Legolas");
if (!Q.empty()) {
std::cout << "The queue is not empty\n";
}
Q.pop();
if (Q.empty()) {
std::cout << "Now it is";
}
}
The queue is not empty
Now it is
The typical approach for consuming a queue uses a while
loop that continues whilst Queue.empty()
is false. Within the body of the loop, we access the front()
object, do what we need to do with it, and then pop()
it off.
It looks something like this:
#include <iostream>
#include <queue>
#include <string>
class Character {/*...*/}
int main() {
std::queue<Character> Q;
Q.emplace("Legolas");
Q.emplace("Gimli");
Q.emplace("Gandalf");
while (!Q.empty()) {
std::cout << Q.front().Name
<< " has entered the game\n";
Q.pop();
}
std::cout << "The queue is empty!";
}
Legolas has entered the game
Gimli has entered the game
Gandalf has entered the game
The queue is empty!
The C++ std::queue is an example of a container adaptor. It does not implement the storage mechanism for our objects - rather, it defers that to some other container, and provides us with an abstraction that acts like a queue.
By default, the container underlying std::queue
is a std::deque
. We cover deques (double-ended queues) in a later lesson.
We can change the underlying container by passing it to the second argument when constructing our queue.
The default std::deque
is appropriate here, but we can use a different container class, including our own if preferred. In the following example, we use a doubly-linked list, std::list
 instead:
#include <iostream>
#include <list>
#include <queue>
#include <string>
class Character {/*...*/}
int main() {
std::queue<Character, std::list<Character>> Q;
Q.emplace("Legolas");
std::cout << Q.front().Name
<< " is at the front of the queue";
}
Legolas is at the front of the queue
For the std::queue
adaptor to be compatible with a container, the container needs to have the following methods:
front()
- returns a reference to the object at the start of the containerback()
- returns a reference to the object at the end of the containerpush_back()
- adds an object to the end of the container. Called by the push()
method of std::queue
.emplace_back()
- constructs an object in the end of the container. Called by the emplace()
method of std::queue
pop_front()
- removes the object at the start of the container. Called by the pop()
method of std::queue
Whilst not enforced by the compiler, our container should naturally be efficient at running those methods if we want our queue to be performant.
Additionally, the underlying container should not consume an excessive amount of resources that are not required when the container is only being used in the context of a queue.
We can initialize a queue using a collection of the queue’s underlying type. For example, if our queue is using a std::list
as the underlying container, we can initialize our queue by copying that std::list
:
#include <iostream>
#include <list>
#include <queue>
#include <string>
class Character {/*...*/}
int main() {
std::list<Character> Characters{
"Legolas", "Gandalf", "Gimli"};
std::queue<Character, std::list<Character>> Q{
Characters};
std::cout << Q.front().Name
<< " is at the front of the queue";
}
When initializing a queue from a container, we can use Class Template Argument Deduction (CTAD) to omit the template parameters. This is because the compiler can examine the type of argument we’re passing to the constructor.
In this case, we’re passing a std::list<Character>
, so the compiler can infer our std::queue
will be containing Character
objects, so we’ll construct a std::queue<Character, std::list>
:
int main() {
std::list<Character> Characters{};
std::queue Q{Characters};
}
If we don’t need to copy our original container to create the queue, we can instead move it using std::move
:
#include <iostream>
#include <list>
#include <queue>
int main() {
std::list Nums{1, 2, 3};
std::queue Q{std::move(Nums)};
std::cout << Q.front()
<< " is at the front of the queue";
}
In this lesson, we covered the queue data structure, and specifically the standard library’s implementation: std::queue
. The key takeaways are:
std::queue
class template provides a convenient way to create and use queues in C++.emplace()
or push()
and removed from the front using pop()
.front()
and back()
methods allow accessing the elements at the front and back of the queue.std::queue
is a container adaptor that can use different underlying containers, such as std::deque
or std::list
.Â
std::queue
Learn the fundamentals and applications of queues with the std::queue
container.
Comprehensive course covering advanced concepts, and how to use them on large-scale projects.