You’ve seen that templates are a good choice for implementing container classes such as the Queue
class. You may be wondering whether having a nested class poses any problems to converting the Queue
class definition to a template. The answer is no. Listing 15.5 shows how this conversion can be made. As is common for class templates, the header file includes the class template, along with method function templates.
// queuetp.h -- queue template with a nested class
#ifndef QUEUETP_H_
#define QUEUETP_H_
template <class Item>
class QueueTP
{
private:
enum {Q_SIZE = 10};
// Node is a nested class definition
class Node
{
public:
Item item;
Node * next;
Node(const Item & i):item(i), next(0){ }
};
Node * front; // pointer to front of Queue
Node * rear; // pointer to rear of Queue
int items; // current number of items in Queue
const int qsize; // maximum number of items in Queue
QueueTP(const QueueTP & q) : qsize(0) {}
QueueTP & operator=(const QueueTP & q) { return *this; }
public:
QueueTP(int qs = Q_SIZE);
~QueueTP();
bool isempty() const
{
return items == 0;
}
bool isfull() const
{
return items == qsize;
}
int queuecount() const
{
return items;
}
bool enqueue(const Item &item); // add item to end
bool dequeue(Item &item); // remove item from front
};
// QueueTP methods
template <class Item>
QueueTP<Item>::QueueTP(int qs) : qsize(qs)
{
front = rear = 0;
items = 0;
}
template <class Item>
QueueTP<Item>::~QueueTP()
{
Node * temp;
while (front != 0) // while queue is not yet empty
{
temp = front; // save address of front item
front = front->next;// reset pointer to next item
delete temp; // delete former front
}
}
// Add item to queue
template <class Item>
bool QueueTP<Item>::enqueue(const Item & item)
{
if (isfull())
return false;
Node * add = new Node(item); // create node
// on failure, new throws std::bad_alloc exception
items++;
if (front == 0) // if queue is empty,
front = add; // place item at front
else
rear->next = add; // else place at rear
rear = add; // have rear point to new node
return true;
}
// Place front item into item variable and remove from queue
template <class Item>
bool QueueTP<Item>::dequeue(Item & item)
{
if (front == 0)
return false;
item = front->item; // set item to first item in queue
items--;
Node * temp = front; // save location of first item
front = front->next; // reset front to next item
delete temp; // delete former first item
if (items == 0)
rear = 0;
return true;
}
#endif
One interesting thing about the template in Listing 15.5 is that Node
is defined in terms of the generic type Item
. Thus, a declaration like the following leads to a Node
defined to hold type double
values:
QueueTp<double> dq;
And the following declaration leads to a Node
defined to hold type char
values:
QueueTp<char> cq;
These two Node
classes are defined in two separate QueueTP
classes, so there is no name conflict between the two. That is, one node is type QueueTP<double>::Node
, and the other is type QueueTP<char>::Node
.
Listing 15.6 offers a short program for testing the new class.
// nested.cpp -- using a queue that has a nested class
#include <iostream>
#include <string>
#include "queuetp.h"
int main()
{
using std::string;
using std::cin;
using std::cout;
QueueTP<string> cs(5);
string temp;
while(!cs.isfull())
{
cout << "Please enter your name. You will be "
"served in the order of arrival.
"
"name: ";
getline(cin, temp);
cs.enqueue(temp);
}
cout << "The queue is full. Processing begins!
";
while (!cs.isempty())
{
cs.dequeue(temp);
cout << "Now processing " << temp << "...
";
}
return 0;
}
Here is a sample run of the program in Listings 15.5 and 15.6:
Please enter your name. You will be served in the order of arrival.
name: Kinsey Millhone
Please enter your name. You will be served in the order of arrival.
name: Adam Dalgliesh
Please enter your name. You will be served in the order of arrival.
name: Andrew Dalziel
Please enter your name. You will be served in the order of arrival.
name: Kay Scarpetta
Please enter your name. You will be served in the order of arrival.
name: Richard Jury
The queue is full. Processing begins!
Now processing Kinsey Millhone...
Now processing Adam Dalgliesh...
Now processing Andrew Dalziel...
Now processing Kay Scarpetta...
Now processing Richard Jury...
Programs sometimes encounter runtime problems that prevent them from continuing normally. For example, a program may try to open an unavailable file, or it may request more memory than is available, or it may encounter values it cannot abide. Usually, programmers try to anticipate such calamities. C++ exceptions provide a powerful and flexible tool for dealing with these situations. Exceptions are a relatively recent addition to C++, so some older compilers haven’t implemented them. Also some compilers turn this feature off by default, so you may have to use the compiler options to turn it on.
Before examining exceptions, let’s look at some of the more rudimentary options available to programmers. As a test case, let’s look at a function that calculates the harmonic mean of two numbers. The harmonic mean of two numbers is defined as the inverse of the average of the inverses. This can be reduced to the following expression:
2.0 × x × y / (x + y)
Note that if y is the negative of x, this formula results in division by zero, a rather undesirable operation. Many newer compilers handle division by zero by generating a special floating-point value that represents infinity; cout
displays this value as Inf
, inf
, INF
, or something similar. Other compilers may produce programs that crash when division by zero occurs. It is best to write code that behaves in the same controlled fashion on all systems.
18.188.211.106