Introduction to Queues and std::queue

Learn the fundamentals and applications of queues with the std::queue container.
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, Unlimited Access
Illustration representing computer hardware
Ryan McCombe
Ryan McCombe
Updated

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.

Diagram showing a queue

Use Cases

Queues are everywhere in programming. Their use cases broadly fall into three categories - simulation, asynchronous communication, and decoupling.

Simulating a Real Queue

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.

Asynchronous Communication between Systems

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.

Decoupling Modules

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;
}

Copying and Moving Queues

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)};
}

Constructing Queues from Other Containers

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.

Constructing Queues from Iterators

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()};
}

Adding (Pushing) Objects

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)); }

Accessing Queue Elements

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.

Removing (Popping) Objects

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

Checking the Queue Size

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

Practical Queue Example

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!

Container Adaptors

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 container
  • back() - returns a reference to the object at the end of the container
  • push_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.

Initializing a Queue from a Container

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"; }

Class Template Argument Deduction

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};
}

Moving the Container

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";
}

Summary

In this lesson, we covered the queue data structure, and specifically the standard library’s implementation: std::queue. The key takeaways are:

  • Queues are FIFO data structures with various applications in programming.
  • The std::queue class template provides a convenient way to create and use queues in C++.
  • Objects are added to the back of the queue using emplace() or push() and removed from the front using pop().
  • The 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.

 

Was this lesson useful?

Next Lesson

Priority Queues using std::priority_queue

Master priority queues with a complete guide, starting from the basics, using the standard library’s priority_queue.
Illustration representing computer hardware
Ryan McCombe
Ryan McCombe
Updated
A computer programmer
This lesson is part of the course:

Professional C++

Comprehensive course covering advanced concepts, and how to use them on large-scale projects.

Free, Unlimited Access
Standard Library Data Structures
Next Lesson

Priority Queues using std::priority_queue

Master priority queues with a complete guide, starting from the basics, using the standard library’s priority_queue.
Illustration representing computer hardware
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved