// --------------------------------------------------------------------------- // SimpleUnboundedQueue.h // // A generic unbounded queue class. (There is already a queue class in the // Standard C++ Library; this class is just an illustration of how such a // class is written from scratch!) Copying and assignment are prohibited // because this is just a simple example. A real class would have copy // constructors and an assignment operator defined. // // Queue() Constructor: makes an empty queue. // ~Queue() Destructor. // q.getSize() Returns the number of elements in the queue. // q.isEmpty() Returns whether the queue is empty. // q.enqueue(x) Adds x to the back of q. Will throw a bad_alloc if there // is no memory available to add the element (but note that // many compilers do not implement the C++ Standard correctly // and with one of these the behavior is undefined is there // is no memory). // q.dequeue() If not empty, removes the front element and returns it, // else throws a standard underflow_error. // q.peek() If not empty, returns the front element, else throws // a standard underflow_error. // --------------------------------------------------------------------------- #ifndef _SIMPLE_UNBOUNDED_QUEUE_H #define _SIMPLE_UNBOUNDED_QUEUE_H #include <stdexcept> using namespace std; // A queue object is represented as a singly-linked list of nodes, from the // head to the tail. The queue itself maintains pointer to the head and the // tail nodes. template<class T> class Queue { // A private local class for the nodes that are linked together. private: class Node { public: T data; Node* next; Node(T data, Node* next): data(data), next(next) {} }; // The queue itself maintains pointers to the head node and tail node. private: Node* head; Node* tail; // A private helper function to delete all the nodes in the queue. // It is inefficient but it looks cool. It deletes all nodes // from its argument onward. private: static void clear(Node* n) {if (n != 0) {clear(n->next); delete n;}} // Constructors and Destructors public: // Constructor ensures that the queue is empty upon creation. Queue(): head(0), tail(0) {} // Destructor uses the helper to delete all the nodes. ~Queue() {clear(head);} // Behavior public: // The size of the queue is determined by mindless counting of nodes. int getSize() { int count = 0; for (Node* n = head; n != 0; n = n->next) count++; return count; } // A queue is empty when its head and tail pointers are both null. Since // the implementation maintains this consistency requirement, it suffices // to simply check one or the other to determine emptiness. bool isEmpty() {return head == 0;} // To add an element we make a new node, attach it after the current tail // node, and update the queue's tail filed to point to this new node. One // special case is when the queue is initially empty: in this case the // new node becomes both the head and the tail. void enqueue(T x) { if (tail == 0) head = tail = new Node(x, 0); else tail = tail->next = new Node(x, 0); } // Removing from a queue is taking the head element out. First of all if // there are no elements we have to throw an error because there is nothing // to remove. If there is, we save the head element to return later as // well as a pointer to the head node to delete later. We "detach" the node // by moving the queue's head pointer past it. Note the special case here! // If this was the last node, we have to update the queue's tail node field! // After all this, we can delete the node and return the value we saved up. T dequeue() { if (isEmpty()) throw underflow_error("cannot remove from empty queue"); Node* nodeToDelete = head; T valueToReturn = head->data; head = head->next; if (head == 0) tail = 0; delete nodeToDelete; return valueToReturn; } // Peek just returns the head element, if there is one. T peek() { if (head == 0) throw underflow_error("Cannot peek into empty queue"); return head->data; } // Prohibit copying and assignment private: Queue(Queue&); Queue& operator=(Queue&); }; #endif
// --------------------------------------------------------------------------- // testunboundedqueue.cpp // // A test script for the SimpleUnboundedQueue class. // --------------------------------------------------------------------------- #include <iostream> #include <cassert> #include <stdexcept> using namespace std; #include "SimpleUnboundedQueue.h" int main(int argc, char** argv) { // Test an integer queue of testSize 200 for empty, getSize, and that // enqueing then dequeueing produces predicatble (FIFO) results. const int testSize = 8; Queue<int> q; assert(q.isEmpty() && q.getSize() == 0); q.enqueue(0); assert(!q.isEmpty() && q.getSize() == 1); for (int i = 1; i < testSize; i++) { q.enqueue(i); } assert(!q.isEmpty() && q.getSize() == testSize); for (int i = 0; i < testSize; i++) { assert(q.peek() == i); assert(q.dequeue() == i); } // Test underflow_errors are properly thrown on dequeues and peeks // when empty. Also test that if an underflow is thrown, the queue // is not disturbed, i.e., it remains empty. assert(q.isEmpty()); bool underflowHappened = false; try { q.dequeue(); } catch (underflow_error e) { underflowHappened = true; } assert(underflowHappened); assert(q.isEmpty()); underflowHappened = false; try { q.peek(); } catch (underflow_error e) { underflowHappened = true; } assert(underflowHappened); cout << "All tests have passed\n"; return 0; }