Lab Sheet 9
Understanding the Concept of Templates and Exception
1. Function Template
Overloaded functions are normally used to perform similar operations on different types of data. If the operations are identical for each type, it is more convenient to use function templates. The programmer writes a single function template definition. Based on the argument types provided explicitly or inferred from calls to this function, the compiler generates separate object codes of functions to handle each function call appropriately.
All function template definitions begin with a keyword template followed by a list of template parameters to the function template enclosed in angle brackets (< and >); each formal type parameter must be preceded by either the keyword class or typename, as in
template <class Type>
Or
template <typename Type>
Or
template< class Type_1, class Type_2>
Let's see an example.
#include<iostream>
using namespace std;
//function template printArray definition
template <class T>
void printArray(T *array,int count)
{
for(int i=0; i<count; i++)
cout<<array[i]<<" ";
cout<<endl;
}
//end of the function
int main()
{
int a[4]= {1,2,3,4};
double b[5]= {1.1,2.2,3.3,4.4,5.5};
char c[6]= "Hello";
cout<<" Array a contains: "<<endl;
printArray(a,4);
cout<<" Array b contains: "<<endl;
printArray(b,5);
cout<<" Array c contains: "<<endl;
printArray(c,5);
return 0;
}
2. Class Template
The template concept can be extended to classes. Class templates are generally used for data storage classes. The approach is similar to that used in the function template. The template keyword before the class definition signals that the entire class is a template, and the type parameter T is used throughout the class in place of a specific data type.
template <class T>
class Stack
{
//data and member functions using argument T
};
Let's see an example.
#include <iostream>
using namespace std;
template <class T>
class Stack
{
private:
T st[100];
int top;
public:
Stack();
void push(T var);
T pop();
};
template <class T>
Stack<T>::Stack()
{
top=-1;
}
template <class T>
void Stack<T>::push(T var)
{
st[++top]=var;
}
//no bounds checking done here — see Exercise 5
template <class T>
T Stack<T>::pop()
{
return st[top--];
}
//no bounds checking done here — see Exercise 5
int main()
{
Stack<float> s1;
s1.push(111.1F);
s1.push(222.2F);
s1.push(333.3F);
cout<<"1 : "<<s1.pop()<<endl;
cout<<"2 : "<<s1.pop()<<endl;
cout<<"3 : "<<s1.pop()<<endl;
Stack<long> s2;
s2.push(123123123L);
s2.push(234234234L);
s2.push(345345345L);
cout<<"1 : "<<s2.pop()<<endl;
cout<<"2 : "<<s2.pop()<<endl;
cout<<"3 : "<<s2.pop()<<endl;
return 0;
}
3. Exception Handling
Exception handling is designed to handle only synchronous exceptions (exceptions caused by the program's own code, such as division by zero or invalid input). The mechanism provides a means to detect and report an exceptional circumstance so that appropriate action can be taken. The mechanism suggests a separate error handling code that performs the following tasks.
Find the problem (Detect the exception)
Inform that an error has occurred (Throw the exception)
Receive the error information (Catch the exception)
Take corrective actions (Handle the exception)
Exception handling is done in the following form.
.........
try
{
..........
throw exception_object; //or call a function that throws exception
.........
}
catch(type arg) //Catches exception
{
......... //Block of statements that handles the exception
}
.........
.........
Let's see the following example.
#include<iostream>
using namespace std;
class ex_demo
{
private:
int a;
int b;
public:
ex_demo() {}
class DIVIDE {}; //empty class for exception
void getdata()
{
cout<<"Enter dividend and divisor:"<<endl;
cin>>a>>b;
}
void divide()
{
if(b!=0)
{
cout<<"Result(A/B) = "<<static_cast<float>(a)/b<<endl;
}
else
{
throw DIVIDE();
}
}
};
int main()
{
ex_demo ex;
ex.getdata();
try
{
ex.divide();
}
catch(ex_demo::DIVIDE)
{
cout<<"Exception: Divide Error"<<endl;
}
return 0;
}
In the above example an empty class DIVIDE is used only as a type identifier for the exception, as its type is sufficient to provide the exception type. To provide more exception details, a detail class with data members, constructors, and member functions can be created, and a complete constructed object can be thrown to provide details about the exception.
4. Multiple Catch Statement
A program can have more than one condition to throw an exception. It is possible to use multiple catch statements with try block. When an exception is thrown, the exception handlers are searched in order for an appropriate match. The first handler that yields a match is executed, after executing the handler; the control goes to the first statement after the last catch block for that try.
The multiple catch construct has the following form.
try
{
//try block
}
catch(type1 arg)
{
//catch block
}
catch(type2 arg)
{
//catch block
}
....
....
catch(typeN arg)
{
//catch block
}
5. Re-throwing an exception
A handler may decide to re-throw the caught exception without processing it. In such a situation, we may simply invoke throw without any arguments as shown below:
throw;
This causes the current exception to be propagated to the next enclosing try/catch block and caught and handled there in a catch block.
Exercises
Create a function called sum() that returns the sum of the elements of an array. Make this function into a template so that it will work with any numerical type. Write a program that applies this function to data of various types.
Write a class template for a queue (first-in, first-out) class. Implement the queue using an array as its data member. Implement functions to add elements to the queue and remove elements. Use exception handling to stop reading from the queue when it is empty and to stop writing when it is full. Define several queues of different data types and add and remove data from them.
Design a generic class template inventory that manages a stock of items using a dynamic array. The template should work for any item type (such as a custom Product class or standard data types) and include member functions to add an item, remove an item, and display the complete inventory. Test the inventory template class to manage integer item IDs. Also, instantiate it for a custom class such as Product with properties like name and price. Overload the stream insertion (<<) operator in the custom class (Product) so that the template's display function prints the product details cleanly.
Write a function template int searchKey(T* array, K key, int size) that searches for a key of type K in an array of objects type T and returns its index. If the key is not found, throw an exception of type KeyNotFoundException. The array may be of any user-defined class (such as Employee, Product, etc) having a data member that represents an ID of any type (e.g., int, char*, etc). Overload the == operator in the user-defined class so that the object's ID is compared with the given key. The searchKey() function template should use this overloaded operator to determine whether the object's ID matches the search key and return the corresponding index of the array.
Write a program that implements a stack (last-in, first-out) class to throw an exception when a user tries to add an item while the stack is full and when the user tries to remove an item while the stack is empty. Throw exceptions in both of the cases and handle these exceptions.
Write a program that implements a user registration system where a user must enter a username, password, and age. Create three custom exception classes: InvalidPasswordException (password is too short, e.g., less than 6 characters), UnderageException (age is less than 18), and DatabaseFullException (simulating storage limits). Implement a registerUser() function that validates the inputs and throws the appropriate exception. If DatabaseFullException occurs, catch it inside registerUser(), log a system error message, and re-throw it to main() to notify the user that registration is temporarily down. Use multiple catch blocks in main() to gracefully handle and display distinct error messages for each scenario.