Templates

We only need to change the definition of Storable in the following implementation of stack to transform it from an integer stack to a stack employee records:

//stack.h

typedef int Storable;

class Stack
{
public:
   enum {SIZE = 100};
   Stack() { sp = 0; }
   Storable top() const;
   void pop();
   void push(const Storable& s);
   bool full() const { return sp == SIZE; }
   bool empty() const { return sp == 0; }
private:
   Storable items[SIZE];
   int sp;
};

 

// stack.cpp

void Stack::push(const Storable& s)
{   
   if (SIZE <= sp) error("Stack is full\n");
   items[sp++] = s;
}

// etc.

What do we do if we want to stack integers and employees in the same program? Storable can't have two definitions. We'll have to duplicate the stack code:

class IntStack { ... };

class EmployeeStack { ... };

A template is a pattern for creating classes (and a class is a pattern for creating objects) or functions. A template looks like an ordinary class or function declaration, but it may contain type (or value) parameters. A type parameter, X, is identified by writing the line:

template <class X>

or

template <typedef X>

The compiler creates instances of a template (i.e., classes or functions) by replacing X by a real type or class (or value).

By defining Storable to be a type parameter, we can get C++ to create stack types for us:

// Stack.h

template <class Storable>
class Stack
{
public:
   enum {SIZE = 100};
   Stack() {sp = 0;}
   Storable top() const;
   void pop();
   void push(const Storable& s);
   bool full() const { return sp == SIZE; }
   bool empty() const { return sp == 0; }
private:
   Storable items[SIZE];
   int sp;
};

 

template <class Storable>
void Stack<Storable>::push(const Storable& s)
{
   if (SIZE <= sp) error("Stack is full\n");
   items[sp++] = s;
}

// etc.

Programmers create instances of a template by specifying a type argument in <> brackets:

// main.cpp (client code)

#include "Stack.h"

void main()
{
   Stack<int> s1;
   Stack<Employee> s2;
   s1.push(42);
   s2.push(Employee("Smith"));
   // etc.
}

Note the complicated syntax used to implement the push function:

template <class Storable>
void Stack<Storable>::push(const Storable& s)
{ ... }

the first line is necessary because push is a template function. Member functions can be templates just as global functions can. In this case, the push function makes several references to Storable, so the compiler needs to be told that Storable is a type parameter. One unavoidable reference to storable occurs in push's qualifier. Push is not a member function of the Stack class. Stack isn't a class, it's a template. Rather push is a member function of the Stack<Storable> class.

Also note that the implementation and declaration of the stack template was entirely contained in stack.h, which was then included in main.cpp. We can't separate the declaration of a template from its implementation. Remember, a template is a recipe used by the compiler to create class declarations. As such the entire recipe, declaration and implementation, must be included in each file where template instances are declared.

STL

We can now better understand the vector template provided by the standard C++ library. In fact, the standard library provides several container types defined as templates: list, deque, stack, queue, map, etc. Collectively, these are known as the standard template library, STL.

Exceptions

A function should validate its inputs, but what should it do if it discovers one of its inputs isn't valid? There are four alternatives:

1. Ignore it. Let the program crash and maybe this time the OS will print a helpful message (assuming we don't have to reboot the computer).

 

2. Flag it. Maybe the client will notice, then he can deal with it. (This is how streams handle input errors.)

 

3. Trap it. Print a useful error message, then gracefully terminate the program. This is ideal for code being debugged, and this is exactly what our error function does.

 

4. Fix it. Trace back to the place where the invalid input first appeared and, if possible, politely ask the user to enter a different value. Otherwise, tell the user to save his work and call customer support.

In the last option, how do we trace back to where the invalid input first appeared? One idea is to return an error token to the calling function when an invalid input is detected. The calling function could either attempt to handle the problem, or it could return the error token to its caller. Thus, the error token could be propagated all the way through the control stack until it reaches a function that is in a position to handle the error.

There are several problems with this strategy. First, if a function returns a double, for example, which value should be designated as the error token? Second, the calling function needs to explicitly check the return value to see if it's an error token.

C++ automates this process with its catch and throw mechanism. Instead of one return channel between called and caller functions, there are two, one for normal return values, the other for special error tokens called exceptions. An exception e is returned to the caller not with the return statement, but with the throw statement: throw e.

Further, the caller need not check for error tokens. If a called function throws an exception, then the caller function implicitly throws the exception.

For example, let's define a class of exceptions we will throw whenever invalid inputs are detected. Our exception objects will encapsulate error messages:

class InputException
{
public:
   InputException(string msg = "Input error!\n")
   {
      gripe = msg;
   }
   string what() { return gripe; }
private:
   string gripe;
};

Here's a (useless) square function that only squares unsigned numbers. It explicitly throws an input exception if it's called with a negative input. For diagnostic purposes it prints entry and exit messages. Executing throw(exp) is like executing a return statement, the function immediately terminates (thus the exit message won't be printed).

// this only squares unsigned numbers:
double square(double x)
{   
   cout << "Entering square\n";
   if (x < 0)
   {      
      InputException exp("Negative input!\n"); // create exception
      throw(exp); // explicitly throw exception
   }
   cout << "Exiting square\n";
   return x * x;

}

The cube function calls the square function. If square(x) throws an exception, then cube implicitly throws an exception at the same point, thus terminating and not printing the exit message.

double cube(double x)
{
   cout << "Entering cube\n";
   double temp = square(x); // may implicitly throw exception
   cout << "Exiting cube\n";
   return x * temp;
}

Alternatively, a function can handle the exception thrown by a called function rather than propagate the exception to its caller. This is done by calling the dangerous function inside a try-catch block:

try
{   // risky function calls:
   STMT;
   STMT;
   ...
   STMT;
}
catch(InputException e)
{   
   // handle input exception here
}
catch(SyntaxException e)
{   
   // handle syntax exception here
}
// more catch blocks could follow

If a statement inside the try block throws a SyntaxException or an InputException, control is immediately transferred to the first line of the appropriate catch block. Further, the thrown exception is bound to the parameter e, so it is available to the handler code. Unless the handler code changes the flow of control, control will fall through to the next statement following the try-catch block.

For example, displayCube() catches the exception implicitly thrown by cube:

double displayCube(double n)
{   
   cout << "Entering dispalyCube\n";
   double result;
   try
   {      
      result = cube(n);
      cout << n << " cubed = " << result << '\n';
   }
   catch(InputException e)
   {      
      cerr << e.what() << '\n'; // for now
   }
   cout << "Exiting displayCube\n";
   return result;
}

Here's a sample main():

int main()
{
   displayCube(3);
   cout << '\n';
   displayCube(-3);
   cout << "Exiting main\n";
   return 0;
}

At the point square is called the control stack looks like this:

Here's the output of main(). Notice that the second time displayCube() is called, the exit statements for cube and square are not printed:

Entering dispalyCube
Entering cube
Entering square
Exiting square
Exiting cube
3 cubed = 27
Exiting displayCube

Entering dispalyCube
Entering cube
Entering square
Negative input!

Exiting displayCube
Exiting main

Standard Exceptions

Unlike the stream library functions which set flags, he newer C++ standard library functions throw exceptions when errors are detected. All exceptions thrown are instances of classes derived from:

class exception
{
public:
   exception() throw();
   exception(const exception& rhs) throw();
   exception& operator=(const exception& rhs) throw();
   virtual ~exception() throw();
   virtual const char *what() const throw();
};

The principle derived classes which can be found in <stdexcept>:

class runtime_error: public exception { ... };
   class range_error: public runtime_error { ... };
   class overflow_error: public runtime_error { ... };
   class underflow_error: public runtime_error { ... };

class logic_error: public exception { ... };
   class domain_error: public logic_error { ... };
   class invalid_argument: public logic_error { ... };
   class length_error: public logic_error { ... };
   class out_of_range: public logic_error { ... };

Runtime errors are errors that are only detectable when the program executes, while logic errors are detectable before the program executes. In other words, runtime errors are thrown when bad things happen to good programs, while logic errors are thrown when bad things happen to bad programs.

We can (and should) create our own exception classes as derived classes of these classes. For example, recall that our error() function throws a runtime_error when DEBUG_MODE is false:

void error(const string& gripe)
{
   if (DEBUG_MODE)
   {
      cerr << "Error: " << gripe << '\n';
      exit(1);
   }
   else
      throw runtime_error(gripe);
}

Example: An Interpreter Framework

Let's divide the stack calculator example into a reusable, interpreter-specific framework, and a stack-specific customization. To keep things simple, the implementation assumes commands, like Results, are no more that dumb strings, so a parser won't be necessary:

typedef string Result;
typedef string Command;

Any context must implement an execute() function:

class Context
{
public:
   virtual Result execute(Command cmmd) = 0;
};

The interpreter framework provides a control loop and maintains a pointer to the context:

class Interpreter
{
public:
   Interpreter(Context* c = 0) {   context = c; }
   void controlLoop();
private:
   Context* context;
};

The interpreter's control loop just passes the user's command to the context's execute function. It is assumed that the context will throw a runtime error exception if the command is problematic:

void Interpreter::controlLoop()
{
   bool more = true;
   Command cmmd;
   Result result;

   while(more)
   {
      cout << "-> ";
      cin >> cmmd;
   
      if (cmmd == "quit")
      {
         more = false;
         cout << "bye\n";
      }
      else
         try
         {
            result = context->execute(cmmd);
            cout << result << '\n';
         }
         catch(runtime_error e)
         {
            cerr << e.what() << '\n';
         }
   } // while
}

Let's use our framework to build the stack calculator. A stack context is a concrete derived class of Context. Taking advantage of multiple inheritance, it is also derived from Stack<Number>. Finally, to add an extra measure of reusability, Number is a template parameter.

const string done = "Done";
const string msg = "Unrecognized command: ";

 

template <class Number>
class StackContext: public Context, public Stack<Number>
{
public:
   Result execute(Command cmmd);
};

The execute() function throws a runtime exception if the command isn't recognized:

template <class Number>
Result StackContext<Number>::execute(Command cmmd)
{
   if (cmmd == "push")
   {
      Number num;
      cin >> num;
      push(num);
      return done;
   }

   else if (cmmd == "top")
   {
      Number num = top();
      return toString(num);
   }

   else if (cmmd == "pop")
   {
      pop();
      return done;
   }

   else if (cmmd == "+")
   {
      Number num1 = top();
      pop();
      Number num2 = top();
      pop();
      push(num1 + num2);
      return done;
   }

   else if (cmmd == "-")
   {
      Number num1 = top();
      pop();
      Number num2 = top();
      pop();
      push(num2 - num1);
      return done;
   }

   else if (cmmd == "*")
   {
      Number num1 = top();
      pop();
      Number num2 = top();
      pop();
      push(num1 * num2);
      return done;
   }

   else if (cmmd == "/")
   {
      Number num1 = top();
      pop();
      Number num2 = top();
      pop();
      push(num2 / num1);
      return done;
   }
   else
   {
      throw (runtime_error(msg + ' ' + cmmd));
      return done;
   }
}

The stack template follows the implementation given earlier, except runtime exceptions are thrown if stack overflow or underflow occurs:

template <class Storable>
void Stack<Storable>::push(const Storable& s)
{
   if (SIZE <= sp)
      throw (runtime_error("Stack is full\n"));
   items[sp++] = s;
}

Here's main:

int main()
{
   StackContext<double> nums;
   Interpreter calc(&nums);
   calc.controlLoop();
   return 0;
}

Here's a sample output of the stack calculator. Notice that errors like unrecognized commands or stack under/over flow don't break the application:

-> push 5
Done
-> push 6
Done
-> add
Unrecognized command: add
-> +
Done
-> top
11
-> +
Stack is empty
->

Problems

Problem

Redo the turtle graphics application as a customization of the interpreter framework.

Problem

Recall that message passing is an alternative way for objects to communicate. The key to a message passing architecture is a dispatcher. A dispatcher maintains a queue of messages and a list of pointers to potential targets (use a vector). The message loop perpetually removes messages from its queue, locates the target, then sends the message to the target. The send() function permits targets to send messages to each other:

Implement and test a framework for building message passing applications.