Oop Lect7
Oop Lect7
Lecture 7
Templates and Exceptions
Templates
There are situations where same algorithm works for different data type.
Suppose a function that returns the maximum of two numbers can work for different data
types
Through generic programming we can have just one function to find max of int or float or
any other type of values
Templates make it possible to use one function or class to handle many different data types.
The template concept can be used in two different ways: with functions and with classes.
We’ll look at function templates first, then go on to class templates, and finally to
exceptions.
Function Templates
Suppose you want to write a function that returns the absolute value of two numbers.
The absolute value of 3 is 3, and the absolute value of –3 is also 3. Normally this function
would be written for a particular data type:
It’s true that in C++ these functions can all be overloaded to have the same name, but you
must nevertheless write a separate definition for each one.
Rewriting the same function body over and over for different types is time-consuming and
wastes space in the listing. Also, if you find you’ve made an error in one such function, you’ll
need to remember to correct it in each function body.
Failing to do this correctly is a good way to introduce inconsistencies into your program.
It would be nice if there were a way to write such a function just once, and have it work for
many different data types. This is exactly what function templates do for you.
A Simple Function Template
Our first example shows how to write our absolute-value function as a template, so that it will
work with any basic numerical type. This program defines a template version of abs() and
then, in main(), invokes this function with different data types to prove that it works.
As you can see, the abs() function now works with all three of the data types (int, long, and
double) that we use as arguments.
Here’s how we specify the abs() function to work with multiple data types:
In the preceding function template, this name is T. (There’s nothing magic about this name;
it can be anything you want, like Type, or anyType, or FooBar.)
The template keyword signals the compiler that we’re about to define a function template.
The keyword class might just as well be called type.
The keyword class is used to define the name of general data type.
As we’ve seen, you can define your own data types using classes, so there’s really no
distinction between types and classes. The variable following the keyword class (T in this
example) is called the template argument.
What the Compiler Does
What does the compiler do when it sees the template keyword and the function definition
that follows it? Well, nothing right away.
The function template itself doesn’t cause the compiler to generate any code. It can’t
generate code because it doesn’t know yet what data type the function will be working
with.
It simply remembers the template for possible future use. Code generation doesn’t take
place until the function is actually called (invoked) by a statement within the program in the
statement
cout << “\nabs(“ << int << “)=” << abs(int1);
When the compiler sees such a function call, it knows that the type to use is int, because
that’s the type of the argument int1.
So it generates a specific version of the abs() function for type int, substituting int wherever it
sees the name T in the function template. This is called instantiating the function template,
and each instantiated version of the function is called a template function.
What the Compiler Does
Similarly, the expression abs(lon1) causes the compiler to generate a version of abs() that
operates on type long and a call to this function, while the abs(dub1) call generates a
function that works on type double.
Of course, the compiler is smart enough to generate only one version of abs() for each
data type.
Thus, even though there are two calls to the int version of the function, the code for this
version appears only once in the executable code.
Notice that the amount of RAM used by the program is the same whether we use the
template approach or actually write three separate functions. The template approaches
simply saves us from having to type three separate functions into the source file.
This makes the listing shorter and easier to understand. Also, if we want to change the way
the function works, we need to make the change in only one place in the listing instead of
three places.
The compiler decides how to compile the function based entirely on the data type used in
the function call’s argument (or arguments). The function’s return type doesn’t enter into
this decision.
Function Templates with Multiple
Arguments
Let’s look at another example of a function template. This one takes three arguments: two
that are template arguments and one of a basic type.
The purpose of this function is to search an array for a specific value. The function returns
the array index for that value if it finds it, or –1 if it can’t find it.
The arguments are array, the value to search for, and the size of the array.
In main() we define four different arrays of different types, and four values to search for. We
treat type char as a number. Then we call the template function once for each array.
Program example
// template used for function that finds number in array
#include <iostream>
using namespace std; int main()
{
//function returns index number of item, cout << “\n d in chrArray: index=” << find(chrArr, ch, 6);
or -1 if not found cout << “\n 6 in intArray: index=” << find(intArr, in, 6);
template <class atype> cout << “\n11 in lonArray: index=” << find(lonArr, lo, 6);
int find(atype array[], atype value, int size) cout << “\n 4 in dubArray: index=” << find(dubArr, db, 6
{ cout << endl;
for(int j=0; j<size; j++) return 0;
if(array[j]==value) }
return j; Output
return -1; d in chrArray: index=3
} 6 in intArray: index=-1
char chrArr[] = {‘a’, ‘b’, ‘c’, ‘d’, ‘e’, ‘f’}; //array 11 in lonArray: index=4
char ch = ‘d’; //value to find 4 in dubArray: index=-1
int intArr[] = {1, 3, 5, 9, 11, 13};
int in = 6;
long lonArr[] = {1L, 3L, 5L, 9L, 11L, 13L};
long lo = 11L;
double dubArr[] = {1.0, 3.0, 5.0, 9.0, 11.0, 13.0};
double db = 4.0;
Template Arguments Must Match
When a template function is invoked, all instances of the same template argument must be of
the same type. For example, in find(), if the array name is of type int, the value to search for
must also be of type int. You can’t say
Because the compiler expects all instances of atype to be the same type. It can
generate a function
Because the first and second arguments must be the same type.
Syntax Variation
Some programmers put the template keyword and the function declarator on the same line:
template<class atype> int find(atype array[], atype value, int size)
{ //function body }
More Than One Template Argument
You can use more than one template argument in a function template. For example,
suppose you like the idea of the find() function template, but you aren’t sure how large an
array it might be applied to.
If the array is too large then type long would be necessary for the array size, instead of
type int.
On the other hand, you don’t want to use type long if you don’t need to. You want to
select the type of the array size, as well as the type of data stored, when you call the
function.
To make this possible, you could make the array size into a template argument as well.
We’ll call it btype:
This has a similar effect to the class template because it performs a simple text substitution
and can thus work with any type.
However, as we’ve noted before, macros aren’t much used in C++. There are several
problems with them.
One is that macros don’t perform any type checking. There may be several arguments to
the macro that should be of the same type, but the compiler won’t check whether or not
they are. Also, the type of the value returned isn’t specified.
In any case, macros are confined to functions that can be expressed in a single statement.
There are also other, more subtle, problems with macros. On the whole it’s best to avoid
them.
Class Templates
The template concept can be extended to classes. Class templates are generally used for
data storage (container) classes. Stacks, queues and linked lists are examples of data-storage
classes.
Limitation: However, the examples of these classes could store data of only a single basic type
class LongStack
class Stack
{
{
private:
private:
long st[MAX]; //array of longs
int st[MAX]; //array of ints
int top; //index number of top of stack
int top; //index number of top of stack
public:
public:
LongStack(); //constructor
Stack(); //constructor
void push(long var); //takes long as argument
void push(int var); //takes int as argument
long pop(); //returns long value
int pop(); //returns int value
};
};
Similarly, we would need to create a new stack class for every data type we wanted to store.
It would be nice to be able to write a single class specification that would work for variables of
all types, instead of a single basic type. As you may have guessed, class templates allow us to
do this.
Class Template Example
// implements stack class as a template int main()
#include <iostream.h> {
using namespace std; Stack<float> s1; //s1 is object of class Stack<float>
const int MAX = 100; //size of array s1.push(1111.1F); //push 3 floats, pop 3 floats
template <class Type> s1.push(2222.2F);
class Stack s1.push(3333.3F);
{ cout << “1: “ << s1.pop() << endl;
private: cout << “2: “ << s1.pop() << endl;
Type st[MAX]; //stack: array of any type cout << “3: “ << s1.pop() << endl;
int top; //number of top of stack
public: Stack<long> s2; //s2 is object of class Stack<long>
Stack() //constructor
{ top = -1; } s2.push(123123123L); //push 3 longs, pop 3 longs
void push(Type var) //put number on stack s2.push(234234234L);
{ st[++top] = var; } s2.push(345345345L);
Type pop() //take number off stack cout << “1: “ << s2.pop() << endl;
{ return st[top--]; } cout << “2: “ << s2.pop() << endl;
}; cout << “3: “ << s2.pop() << endl;
return 0;
}
Class Template
Here the class Stack is presented as a template class. The approach is similar to that used in
function templates. The template keyword and class Stack signal that the entire class will be a
template.
Stack<float> s1;
This creates an object, s1, a stack that stores numbers of type float. The compiler provides
space in memory for this object’s data, using type float wherever the template argument Type
appears in the class specification. It also provides space for the member functions
If the member functions are defined externally (outside of the class specification), we
need a new syntax. The next program shows how this works
template<class Type>
void Stack<Type>::push(Type var)
{
st[++top] = var;
}
The name Stack<Type> is used to identify the class of which push() is a member.
In a normal non-template member function the name Stack alone would suffice:
Exception Syntax
Ordinarily the application’s calls to the class member functions cause no problems.
This member function then informs the application that an error has occurred.
When exceptions are used, this is called throwing an exception.
Exceptions..
In the application we install a separate section of code to handle the error.
This code is called an exception handler or catch block; it catches the exceptions thrown by
the member function.
Any code in the application that uses objects of the class is enclosed in a try block. Errors
generated in the try block will be caught in the catch block. Code that doesn’t interact with
the class need not be in a try block
The exception mechanism uses three new C++ keywords: throw, catch, and try. Also, we need
to create a new kind of entity called an exception class
Exception Mechanism
Program Skeleton
class AClass //a class int main() //application
{ {
public: try //try block
class AnError //exception class {
{ AClass obj1; //interact with AClass objects
}; obj1.Func(); //may cause error
void Func() //a member function }
{ catch(AClass::AnError) //exception handler
if( /* error condition */ ) { //(catch block)
throw AnError(); //throw exception //tell user about error, etc.
} }; } return 0; }
We start with a class called AClass, which represents any class in which errors might
occur. An exception class, AnError, is specified in the public part of AClass. In AClass’s
member functions we check for errors. If we find one, we throw an exception, using the
keyword throw followed by the constructor for the error class:
In the main() part of the program we enclose any statements that interact with AClass
in a try block. If any of these statements causes an error to be detected in an AClass
member function, an exception will be thrown and control will go to the catch block
that immediately follows the try block.
A Simple Exception Example
Let’s look at a working program example that uses exceptions.
The application program might attempt to push too many objects onto the stack, thus
exceeding the capacity of the array, or it might try to pop too many objects off the stack,
thus obtaining invalid data. In the program we use an exception to handle these two
errors.
Stack() //constructor
// demonstrates exceptions { top = -1; }
#include <iostream> void push(int var)
using namespace std; {
const int MAX = 3; //stack holds 3 integers if(top >= MAX-1) //if stack full,
class Stack throw Range(); //throw exception
{ st[++top] = var; //put number on stack
private: }
int st[MAX]; //array of integers int pop()
int top; //index of top of stack {
public: if(top < 0) //if stack empty,
class Range //exception class for Stack throw Range(); //throw exception
{ //note: empty class body return st[top--]; //take number off stack
}; }
};
A Simple Exception Example
int main() {
Stack s1;
try
{
s1.push(11);
s1.push(22);
s1.push(33);
// s1.push(44); //oops: stack full
cout << “1: “ << s1.pop() << endl;
cout << “2: “ << s1.pop() << endl;
cout << “3: “ << s1.pop() << endl;
cout << “4: “ << s1.pop() << endl; //oops: stack empty
}
catch(Stack::Range) //exception handler
{
cout << “Exception: Stack Full or Empty” << endl;
}
cout << “Arrive here after catch (or normal exit)” << endl;
return 0;
}
Program Working
Note that we’ve made the stack small so that it’s easier to trigger an exception by pushing
too many items.
Let’s examine the features of this program that deal with exceptions. There are four of them. In
the class specification there is an exception class. There are also statements that throw
exceptions.
In the main() part of the program there is a block of code that may cause exceptions (the try
block), and a block of code that handles the exception (the catch block).
The program first specifies an exception class within the Stack class:
class Range
{ //note: empty class body
};
Here the body of the class is empty, so objects of this class have no data and no member
functions.
All we really need in this simple example is the class name, Range. This name is used to
connect a throw statement with a catch block. (The class body need not always be empty,
as we’ll see later.)
Program Working
Throwing an Exception
In the Stack class an exception occurs if the application tries to pop a value when the
stack is empty or tries to push a value when the stack is full.
To let the application know that it has made such a mistake when manipulating a Stack
object, the member functions of the Stack class check for these conditions using if
statements, and throw an exception if they occur.
The Range() part of this statement invokes the implicit constructor for the Range class,
which creates an object of this class. The throw part of the statement transfers program
control to the exception handler
Program Working
The try Block
All the statements in main() that might cause this exception that is, statements that
manipulate Stack objects are enclosed in braces and preceded by the try keyword:
try
{
//code that operates on objects that might cause an exception
}
This is simply part of the application’s normal code; it’s what you would need to write even if
you weren’t using exceptions.
Not all the code in the program needs to be in a try block; just the code that interacts with
the Stack class. Also, there can be many try blocks in your program, so you can access
Stack objects from different places.
Program Working
The Exception Handler (Catch Block)
The code that handles the exception is enclosed in braces, preceded by the catch keyword,
with the exception class name in parentheses. The exception class name must include the
class in which it is located. Here it’s Stack::Range.
catch(Stack::Range)
{
//code that handles the exception
}
This construction is called the exception handler. It must immediately follow the try block. In
program the exception handler simply prints an error message to let the user know why the
program failed.
Control “falls through” the bottom of the exception handler, so you can continue processing
at that point. Or the exception handler may transfer control elsewhere, or (often) terminate
the program.
Summary
Sequence of Events
// demonstrates two exception handlers void push(int var) //put number on stack
#include <iostream> {
using namespace std; if(top >= MAX-1) //if stack full,
const int MAX = 3; //stack holds 3 integers throw Full(); //throw Full exception
class Stack st[++top] = var;
{ }
private: int pop() //take number off stack
int st[MAX]; //stack: array of integers {
int top; //index of top of stack if(top < 0) //if stack empty,
public: throw Empty(); //throw Empty exception
class Full { }; //exception class return st[top--];
class Empty { }; //exception class }
Stack() //constructor };
{ top = -1; }
Multiple Exceptions
int main()
catch(Stack::Full)
{
{
Stack s1;
cout << “Exception: Stack Full” << endl;
try
}
{
catch(Stack::Empty)
s1.push(11);
{
s1.push(22);
cout << “Exception: Stack Empty” <<
s1.push(33);
endl;
// s1.push(44); //oops: stack full
}
cout << “1: “ << s1.pop() << endl;
return 0;
cout << “2: “ << s1.pop() << endl;
}
cout << “3: “ << s1.pop() << endl;
cout << “4: “ << s1.pop() << endl; //oops: stack empty
}
Program Working
we specify two exception classes:
class Full { };
class Empty { };
catch(Stack::Full)
{ //code to handle Full exception }
catch(Stack::Empty)
{ //code to handle Empty exception }
All the catch blocks used with a particular try block must immediately follow the try block. In
this case each catch block simply prints a message: “Stack Full” or “Stack Empty”. Only one
catch block is activated for a given exception. A group of catch blocks, or a catch ladder,
operates a little like a switch statement, with only the appropriate section of code being
executed. When an exception has been handled, control passes to the statement following
all the catch blocks.
Exceptions with the Distance Class
Let’s look at another example of exceptions, this one applied to the infamous Distance class
from previous chapters.
A Distance object has an integer value for feet and a floating-point value for inches. The
inches value should always be less than 12.0.
A problem with this class in previous examples has been that it couldn’t protect itself if the
user initialized an object with an inches value of 12.0 or greater. This could lead to trouble
when the class tried to perform arithmetic, since the arithmetic routines (such as operator +())
assumed inches would be less than 12.0.
Such impossible values could also be displayed, thus confounding the user with dimensions
like 7’–15”.
Let’s rewrite the Distance class to use an exception to handle this error,
Program Example
// exceptions with Distance class void getdist() //get length from user
#include <iostream> {
using namespace std; cout << “\nEnter feet: “; cin >> feet;
class Distance //English Distance class cout << “Enter inches: “; cin >> inches;
{ if(inches >= 12.0) //if inches too big,
private: throw InchesEx(); //throw exception
int feet; }
float inches; void showdist() //display distance
public: {
class InchesEx { }; //exception class cout << feet << “\’-” << inches << ‘\”’; }
};
Distance() //constructor (no args)
{ feet = 0; inches = 0.0; }
For instance, in the previous example, it might help the programmer to know what the bad
inches value actually was.
Is there a way to pass such information from the member function, where the exception is
thrown, to the application that catches it?
You can answer this question if you remember that throwing an exception involves not only
transferring control to the handler, but also creating an object of the exception class by calling
its constructor. In previous example , for example, we create an object of type InchesEx when
we throw the exception with the statement
throw InchesEx();
If we add data members to the exception class, we can initialize them when we create the
object. The exception handler can then retrieve the data from the object when it catches the
exception.
Program Example
// exceptions with arguments Distance() //constructor (no args)
#include <iostream> { feet = 0; inches = 0.0; }
#include <string> //--------------------------------------------------------------
using namespace std; Distance(int ft, float in) //constructor (two args)
{
class Distance //English Distance class if(in >= 12.0)
{ throw InchesEx(“2-arg constructor”, in);
private: feet = ft;
int feet; inches = in;
float inches; }
public: //--------------------------------------------------------------
void getdist() //get length from user
class InchesEx //exception class {
{ cout << “\nEnter feet: “; cin >> feet;
public: cout << “Enter inches: “; cin >> inches;
string origin; //for name of routine if(inches >= 12.0)
float iValue; //for faulty inches value throw InchesEx(“getdist() function”, inches);
InchesEx(string or, float in) //2-arg constructor }
{
origin = or; //store string
iValue = in; //store inches
}};
Program Example
void showdist() //display distance
{ cout << feet << “\’-” << inches << ‘\”’; }
};
int main()
{
try
{
Distance dist1(17, 3.5); //2-arg constructor
Distance dist2; //no-arg constructor
dist2.getdist(); //get value
//display distances
cout << “\ndist1 = “; dist1.showdist();
cout << “\ndist2 = “; dist2.showdist();
}
catch(Distance::InchesEx ix) //exception handler
{
cout << “\nInitialization error in “ << ix.origin
<< “.\n Inches value of “ << ix.iValue
<< “ is too large.”;
}
cout << endl;
return 0; }
Program Working
There are three parts to the operation of passing data when throwing an exception: specifying
the data members and a constructor for the exception class, initializing this constructor when
we throw an exception, and accessing the object’s data when we catch the exception.
It’s convenient to make the data in an exception class public so it can be accessed directly by
the exception handler. Here’s the specification for the new InchesEx exception class:
class InchesEx //exception class
{
public:
string origin; //for name of routine
float iValue; //for faulty inches value
InchesEx(string or, float in) //2-arg constructor
{
origin = or; //put string in object
iValue = in; //put inches value in object
}
};
Program Working
Initializing an Exception Object
How do we initialize the data when we throw an exception? In the two-argument constructor
for the Stack class we say
When the exception is thrown, the handler will display the string and inches values. The string
will tell us which member function is throwing the exception, and the value of inches will report
the faulty inches value detected by the member function.
This additional data will make it easier for the programmer or user to figure out what caused
the error.
Program Working
Extracting Data from the Exception Object
How do we extract this data when we catch the exception? The simplest way is to make the
data a public part of the exception class, as we’ve done here. Then in the catch block we
can declare ix as the name of the exception object we’re catching. Using this name we can
refer to its data in the usual way, using the dot operator:
catch(Distance::InchesEx ix)
{//access ‘ix.origin’ and ‘ix.iValue’ directly }
We can then display the value of ix.origin and ix.iValue. Here’s some interaction when the
user enters too large a value for inches:
Enter feet: 7
Enter inches: 13.5
If you set up the appropriate try and catch blocks, you can make use of bad_alloc with very
little effort. Here’s a short example
Put all the statements that use new in a try block. The catch block that follows handles the
exception, often by displaying an error message and terminating the program.
Program Output