Let's Build a Queue

What is a queue?


Things We might Do with a Queue


Terminology. . .

An item gets into the queue at the back or tail.

The item waits and waits, while advancing towards the front or head of the queue.

The item at the head of the queue is the next one to leave the queue.


Diagrams. . .

enqueue A









Now, enqueue B









Now, enqueue C









And last, dequeue









A linked data structure works well for a queue.

Make it doubly linked for ease of implementation. . .

The Queue class appears as


                /-----\
     head -->  |       | <-- tail
               |       |
                \-----/

member functions:

  1. construct an empty queue
  2. a copy constructor, so we can correctly pass Queue objects using call by value semantics
  3. enqueue
  4. dequeue

(There are likely lots of other reasonable member functions, but this is a simplified example.)

What are we putting into the queue?

Assume for this example that we are working with Widget class objects.

In reality, we would use templates to implement a type-independent queue class.


An incomplete class declaration:

 class Queue {
   public:
     Queue(); //construct an empty queue
     Queue(const Queue &); //copy constructor
     void enqueue(Widget *);
     Widget * dequeue();

   private:
     ??? * head;
     ??? * tail;
 };

A typical implementation of a linked data structure uses a struct (not an object, just a structure!), that is most often called a Node.


 struct Node {
   Widget * w;
   Node * next;
   Node * previous;
 };

The fields are all pointers.


A complete diagram of the queue when it has 3 Widget's in it:











Therefore, the declarations of head and tail are:

   Node * head;
   Node * tail;

The Node structure is of use internally to the queue class, so make it private.

A more complete declaration of the queue class:

 class Queue {
   public:
     Queue(); //construct an empty queue
     Queue(const Queue &); //copy constructor
     void enqueue(Widget *);
     Widget * dequeue();

   private:

     struct Node {
       Widget * w;
       Node * next;
       Node * previous;
     };

     Node * head;
     Node * tail;
 };

And, place this declaration into a .h file, using a #ifndef to avoid double "includes," which the compiler hates.

 #ifndef QUEUE_H
 #define QUEUE_H

 class Queue {
   public:
     Queue(); //construct an empty queue
     Queue(const Queue &); //copy constructor
     void enqueue(Widget *);
     Widget * dequeue();

   private:

     struct Node {
       Widget * w;
       Node * next;
       Node * previous;
     };

     Node * head;
     Node * tail;
 };

#endif

To play with a Queue,
we must instantiate one.


Queue *pQ1;

pQ1 = new Queue(); // "new" invokes constructor


Before we can enqueue a Widget,
we need one:


Widget * pW = new Widget();




(*pQ1).enqueue(pW); 





Here is another Queue,
instantiated in a different way:


Queue Q2;  // declaration invokes constructor



But, do not ever do this:


Q2.enqueue(pW);

Why? This gives us 2 distinct queues pointing to only 1 Widget! It makes memory management impossible!









Why discuss this seemingly silly case? Because lots of programmers accidentally make this error.


How to get another Widget instance?

Use the copy constructor, if an exact copy of that same one is needed.


Widget * pW2 = new Widget(*pW);




Copyright © Karen Miller, 2009