// Definition of a Node for the linked-list used by the Queue internally
// To allow 'Queue' class to access private members of 'Node' class
// Parameterized constructor member initialization
Node (const int val) : data(val), next(nullptr) {}
Node *front; // Points to first (front-most) element in queue
Node *rear; // Points to last (rear-most) element in queue
size_t size; // Denotes number of elements in queue
// Default constructor member initialization
Queue () : front(nullptr), rear(nullptr), size(0) {}
// Check if queue is empty (doesn't modify state)
// Queue is empty when front is not pointing to anything
return (front == nullptr);
// Returns number of elements present in queue (doesn't modify state)
const size_t getSize () { return size; }
// Returns first element, present at front of queue (doesn't modify state)
throw underflow_error("Cannot access front element in empty queue");
// Returns last element, present at rear of queue (doesn't modify state)
throw underflow_error("Cannot access rear element in empty queue");
// Insert an element at end of queue
void enque (const int val) {
// Allocate memory for the new element
Node *newNode = new Node(val);
if (isEmpty()) { // The queue was empty before
// Both front and rear will point to the only existing inserted
} else { // The queue had one or more existing elements before
// Attach new node after the old rear
// Updated rear of queue is the new node
size++; // Increment size after insertion
// Remove an element from front of queue
throw underflow_error("Cannot remove element from empty queue");
// Store the first node's address
Node *nodeToDelete = front;
// Make front point to the node after first (can be null)
if (!front) { // When there was only one existing element (it's next is
// Both front and rear would become null after deletion in this case
delete nodeToDelete; // Deallocate the first node's memory
size--; // Decrement size after deletion
// Destructor to deallocate memory of all items of queue
// Keep deque-ing items till queue isn't empty
// Create an instance of our 'Queue' class to store jobs scheduled
cout << "size=" << jobs.getSize() << endl;
Access/Delete operations on empty queue will throw underflow
cout << jobs.first() << endl;
// UNDERFLOW: Cannot access top element in empty stack
cout << jobs.last() << endl;
// UNDERFLOW: Cannot access rear element in empty queue
// UNDERFLOW: Cannot remove element from empty queue
cout << "size=" << jobs.getSize() << ", first=" << jobs.first()
<< ", last=" << jobs.last() << endl;
// size=1, first=12, last=12
cout << "size=" << jobs.getSize() << ", first=" << jobs.first()
<< ", last=" << jobs.last() << endl;
// size=5, first=12, last=88
cout << "size=" << jobs.getSize() << ", first=" << jobs.first()
<< ", last=" << jobs.last() << endl;
// size=3, first=28, last=88
} catch (underflow_error const &ue) {
cout << "UNDERFLOW: " << ue.what() << endl;
} catch (overflow_error const &oe) {
cout << "OVERFLOW: " << oe.what() << endl;
} catch (exception const &e) {
cout << "Other exception: " << e.what() << endl;