UNIT 5
Exception Handling and Generic Programming
- Explain Exception: Errors
Answer: Exceptions provide a way to react to exceptional circumstances (like runtime errors) in programs by transferring control to special functions called handlers.
To catch exceptions, a portion of code is placed under exception inspection. This is done by enclosing that portion of code in a try-block. When an exceptional circumstance arises within that block, an exception is thrown that transfers the control to the exception handler. If no exception is thrown, the code continues normally and all handlers are ignored.
An exception is thrown by using the throw keyword from inside the try block. Exception handlers are declared with the keyword catch, which must be placed immediately after the try block:
1 | // exceptions #include <iostream> Usingnamespacestd;
Int main () { Try { Throw 20; } Catch (int e) { Cout<< "An exception occurred. Exception Nr. " << e << '\n'; } Return 0; } | An exception occurred. Exception Nr. 20 |
|
The code under exception handling is enclosed in a try block. In this example this code simply throws an exception:
| Throw 20; |
|
A throw expression accepts one parameter (in this case the integer value 20), which is passed as an argument to the exception handler.
The exception handler is declared with the catch keyword immediately after the closing brace of the try block. The syntax for catch is similar to a regular function with one parameter. The type of this parameter is very important, since the type of the argument passed by the throw expression is checked against it, and only in the case they match, the exception is caught by that handler.
Multiple handlers (i.e., catch expressions) can be chained; each one with a different parameter type. Only the handler whose argument type matches the type of the exception specified in the throw statement is executed.
If an ellipsis (...) is used as the parameter of catch, that handler will catch any exception no matter what the type of the exception thrown. This can be used as a default handler that catches all exceptions not caught by other handlers:
1 | Try { // code here } Catch (intparam) { cout<< "int exception"; } Catch (charparam) { cout<< "char exception"; } Catch (...) { cout<< "default exception"; } |
|
In this case, the last handler would catch any exception thrown of a type that is neither int nor char.
After an exception has been handled the program, execution resumes after the try-catch block, not after the throw statement!.
It is also possible to nest try-catch blocks within more external try blocks. In these cases, we have the possibility that an internal catch block forwards the exception to its external level. This is done with the expression throw; with no arguments. For example:
1 | Try { Try { // code here } Catch (int n) { Throw; } } Catch (...) { Cout<< "Exception occurred"; } |
|
2. What is Exception specification?
Answer: Older code may contain dynamic exception specifications. They are now deprecated in C++, but still supported. A dynamic exception specification follows the declaration of a function, appending a throw specifier to it. For example:
| Doublemyfunction (charparam) throw (int); |
|
This declares a function called myfunction, which takes one argument of type char and returns a value of type double. If this function throws an exception of some type other than int, the function calls std::unexpected instead of looking for a handler or callingstd::terminate.
If this throw specifier is left empty with no type, this means that std::unexpected is called for any exception. Functions with no throw specifier (regular functions) never callstd::unexpected, but follow the normal path of looking for their exception handler.
1 | Intmyfunction (intparam) throw(); // all exceptions call unexpected Intmyfunction (intparam); // normal exception handling |
|
Standard exceptions
The C++ Standard library provides a base class specifically designed to declare objects to be thrown as exceptions. It is called std::exception and is defined in the <exception> header. This class has a virtual member function called what that returns a null-terminated character sequence (of type char *) and that can be overwritten in derived classes to contain some sort of description of the exception.
1 | // using standard exceptions #include <iostream> #include <exception> Usingnamespacestd;
Classmyexception: public exception { Virtualconstchar* what() constthrow() { Return "My exception happened"; } } myex;
Int main () { Try { Throwmyex; } Catch (exception& e) { Cout<<e.what() << '\n'; } Return 0; } | My exception happened. |
|
We have placed a handler that catches exception objects by reference (notice the ampersand & after the type), therefore this catches also classes derived from exception, like our myex object of type myexception.
3. What are the Types of Errors?
Answer: We face different kinds of errors. These errors can be categorized into five different types. These are like below −
- Syntax Error
- Run-Time Error
- Linker Error
- Logical Error
- Semantic Error
Let us see these errors one by one −
Syntax error
This kind of errors are occurred, when it violates the rule of C++ writing techniques or syntaxes. This kind of errors are generally indicated by the compiler before compilation. Sometimes these are known as compile time error.
In this example, we will see how to get syntax error if we do not put semicolon after one line.
Example
#include<stdio.h>
Main() {
Printf("Hello World")
}
Output
Error] expected ';' before '}' token
Rumtime error
This kind of errors are occurred, when the program is executing. As this is not compilation error, so the compilation will be successfully done. We can check this error if we try to divide a number with 0.
Example
#include<stdio.h>
Main() {
Int x = 52;
Int y = 0;
Printf("Div : %f", x/y);
}
Output
Program crashes during runtime.
Linker error
This kind of errors are occurred, when the program is compiled successfully, and trying to link the different object file with the main object file. When this error is occurred, the executable is not generated, For example some wrong function prototyping, incorrect header file etc. If the main() is written as Main(), this will generate linked error.
Example
#include<stdio.h>
Main() {
Int x = 52;
Int y = 0;
Printf("Div : %f", x/y);
}
Output
C:\crossdev\src\mingw-w64-v3-git\mingw-w64-crt\crt\crt0_c.cundefined reference to WinMain'
Logical error
Sometimes, we may not get the desired output. If the syntax and other things are correct, then also, we may not get correct output due to some logical issues. These are called the logical error. Sometimes, we put a semicolon after a loop, that is syntactically correct, but will create one blank loop. In that case, it will show desired output.
Example
#include<stdio.h>
Main() {
Inti;
For(i = 0; i<5; i++); {
Printf("Hello World");
}
}
Output
Here we want the line will be printed five times. But only one time it will be printed for the block of code.
Semantic error
This kind of error occurs when it is syntactically correct but has no meaning. This is like grammatical mistakes. If some expression is given at the left side of assignment operator, this may generate semantic error.
Example
#include<stdio.h>
Main() {
Int x, y, z;
x = 10;
y = 20;
x + y = z;
}
Output
[Error] lvalue required as left operand of assignment
4. Explain Exception-Handling Fundamentals
Answer: Exceptions are the runtime errors or anomalies that a program may encounter while program execution and stops the normal execution of program. Anomalies can consist conditions such as division by zero, access to array element out of found, running out of memory etc.
Exception handling helps to manage the execution of program when an exception occurs during runtime in a systematic manner. Exception handing mechanism allows smooth execution of program. Exception handling mechanisms in C++ is basically done using three keywords, try, throw and catch.
5. Explain Uncaught Exception
Answer: Usually, the exception is handled by the try-catch block, but there are instances where there isn’t a matching catch block and the program just terminates. This terminate() function is modifiable as per user requirements.
Example
#include <exception>
#include <iostream>
Using namespace std;
//defining custom terminator
Voidmyhandler(){
Cout<< "Inside new terminate handler\n";
Abort();
}
Int main(){
Set_terminate(myhandler);
Try {
Cout<< "Inside try block\n";
Throw 100;
}
Catch (char a){
Cout<< "Inside catch block\n";
}
Return 0;
}
Output
Inside try block
Inside new terminate handler
6. Explain Using try and Catch
Answer: Exception handling is performed using try/catch statement. The C++ try block is used to place the code that may occur exception. The catch block is used to handle the exception.
Example without try/catch
#include <iostream>
Using namespace std;
Float division(int x, int y) {
Return (x/y);
}
Int main () {
Int i = 50;
Int j = 0;
Float k = 0;
k = division(i, j);
Cout << k << endl;
Return 0;
}
Output:
Floating point exception (core dumped)
Try/catch example
#include <iostream>
Using namespace std;
Float division(int x, int y) {
If( y == 0 ) {
Throw "Attempted to divide by zero!";
}
Return (x/y);
}
Int main () {
Int i = 25;
Int j = 0;
Float k = 0;
Try {
k = division(i, j);
Cout << k << endl;
}catch (const char* e) {
Cerr << e << endl;
}
Return 0;
}
Output:
Attempted to divide by zero!
7. Explain Multiple Catch Clauses
Answer: Multiple catch blocks are used when we have to catch a specific type of exception out of many possible type of exceptions i.e. an exception of type char or int or short or long etc. Let's see the use of multiple catch blocks with an example.
Multiple Catch Block Example
#include<iostream>
Using namespace std;
Int main()
{
Int a=10, b=0, c;
Try
{
//if a is divided by b(which has a value 0);
If(b==0)
Throw(c);
Else
c=a/b;
}
Catch(char c) //catch block to handle/catch exception
{
Cout<<"Caught exception : char type ";
}
Catch(inti) //catch block to handle/catch exception
{
Cout<<"Caught exception : int type ";
}
Catch(short s) //catch block to handle/catch exception
{
Cout<<"Caught exception : short type ";
}
Cout<<"\n Hello";
}
Output is -:
Caught exception :int type
Hello
In this code, we have three catch blocks associated with one try block. At the runtime, compiler finds a division-by-zero problem, hence an exception of type int is thrown when a statement enclosed within the try block is executed.
This exception type is matched with the declared exception type in every catch-block(matching starts with the first catch block).
The catch block that matches with type of exception thrown is executed, while the rest of catch blocks are skipped. Let's see how -
- The first catch block has declared an exception of type char, which doesn't match with the exception int thrown by the try block, hence this catch-block is not executed.
- The second catch block declared with an int matches with the exception int thrown by the try block and hence second catch block will be executed.
- As, we have already found the matching catch block, hence, the third catch block with declared exception of type short is skipped, not checked and not executed.
A catch block for all types exceptions
We could declare a catch block which catches all types of exceptions, irrespective of their types. In such catch block, instead of declaring the type of exception it can catch, we are going to use three dots(...) within its parenthesis. Let's see an example.
Let's understand this by an example -
#include<iostream>
Using namespace std;
Int main()
{
Int a=10, b=0, c;
Try
{
//if a is divided by b(which has a value 0);
If(b==0)
Throw(c);
Else
c=a/b;
}
Catch(...) //catch block to handle/catch exception
{
Cout<<"A catch block for all types of exceptions has caught an exception";
}
}
Output
A catch block for all types of exceptions has caught an exception
As you may see in the code above, a single catch block defined with three dots (...) has caught all types of exceptions i.e. an exception of type int, without even declaring it.
8. Explain Nested Try Statements
Answer: When try blocks are nested and a throw occurs in a function called by an inner try block, control is transferred outward through the nested try blocks until the first catch block is found whose argument matches the argument of the throw expression.
For example:
Try
{
Func1();
Try
{
Func2();
}
Catch (spec_err) { /* ... */ }
Func3();
}
Catch (type_err) { /* ... */ }
// if no throw is issued, control resumes here.
In the above example, if spec_err is thrown within the inner try block (in this case, from func2()), the exception is caught by the inner catch block, and, assuming this catch block does not transfer control, func3() is called. If spec_err is thrown after the inner try block (for instance, by func3()), it is not caught and the function terminate() is called. If the exception thrown from func2() in the inner try block is type_err, the program skips out of both try blocks to the second catch block without invoking func3(), because no appropriate catch block exists following the inner try block.
You use a try block to indicate which areas in your program that might throw exceptions you want to handle immediately. You use a function try block to indicate that you want to detect exceptions in the entire body of a function.
Try block syntax
try
{statements } handler
Function try block syntax
try
:member_initializer_listfunction_body handler
The following code is an example of a function try block with a member initializer, a function try block and a try block:
#include <iostream>
Using namespace std;
Class E {
Public:
Const char* error;
E(const char* arg) : error(arg) { }
};
Class A {
Public:
Inti;
// A function try block with a member
// initializer
A() try : i(0) {
Throw E("Exception thrown in A()");
}
Catch (E& e) {
Cout<<e.error<<endl;
}
};
// A function try block
Void f() try {
Throw E("Exception thrown in f()");
}
Catch (E& e) {
Cout<<e.error<<endl;
}
Void g() {
Throw E("Exception thrown in g()");
}
Int main() {
f();
// A try block
Try {
g();
}
Catch (E& e) {
Cout<<e.error<<endl;
}
Try {
A x;
}
Catch(...) { }
}
See the following output of the above example:
Exception thrown in f()
Exception thrown in g()
Exception thrown in A()
The constructor of class A has a function try block with a member initializer. Function f() has a function try block. The main() function contains a try block.
9. Explain User Define Exception using Throw
Answer: The new exception can be defined by overriding and inheriting exception class functionality.
User-defined exception example
Let's see the simple example of user-defined exception in which std::exception class is used to define the exception.
- #include <iostream>
- #include <exception>
- Using namespace std;
- Class MyException : public exception{
- Public:
- Const char * what() const throw()
- {
- Return "Attempted to divide by zero!\n";
- }
- };
- Int main()
- {
- Try
- {
- Int x, y;
- Cout << "Enter the two numbers : \n";
- Cin >> x >> y;
- If (y == 0)
- {
- MyException z;
- Throw z;
- }
- Else
- {
- Cout << "x / y = " << x/y << endl;
- }
- }
- Catch(exception& e)
- {
- Cout << e.what();
- }
- }
Output:
Enter the two numbers :
10
2
x / y = 5
Output:
Enter the two numbers :
10
0
Attempted to divide by zero!
-->
Note: In above example what() is a public method provided by the exception class. It is used to return the cause of an exception.
10. What are Generics? Explain.
Answer: Generics is the idea to allow type (Integer, String, …etc and user-defined types) to be a parameter to methods, classes and interfaces. For example, classes like an array, map, etc, which can be used using generics very efficiently. We can use them for any type.
The method of Generic Programming is implemented to increase the efficiency of the code. Generic Programming enables the programmer to write a general algorithm which will work with all data types. It eliminates the need to create different algorithms if the data type is an integer, string or a character.
The advantages of Generic Programming are
- Code Reusability
- Avoid Function Overloading
- Once written it can be used for multiple times and cases.
Generics can be implemented in C++ using Templates. Template is a simple and yet very powerful tool in C++. The simple idea is to pass data type as a parameter so that we don’t need to write the same code for different data types. For example, a software company may need sort() for different data types. Rather than writing and maintaining the multiple codes, we can write one sort() and pass data type as a parameter.
Generic Functions using Template:
We write a generic function that can be used for different data types. Examples of function templates are sort(), max(), min(), printArray()
#include <iostream> Usingnamespacestd;
// One function works for all data types. // This would work even for user defined types // if operator '>' is overloaded Template<typenameT>
T myMax(T x, T y) { Return(x > y) ? x : y; }
Intmain() {
// Call myMax for int Cout<<myMax<int>(3, 7) <<endl;
// call myMax for double Cout<<myMax<double>(3.0, 7.0) <<endl;
// call myMax for char Cout<<myMax<char>('g', 'e') <<endl;
Return0; } |
Output:
7
7
g
Generic Class using Template:
Like function templates, class templates are useful when a class defines something that is independent of data type. Can be useful for classes like LinkedList, binary tree, Stack, Queue, Array, etc.
Following is a simple example of template Array class.
#include <iostream> Usingnamespacestd;
Template<typenameT> ClassArray { Private: T* ptr; Intsize;
Public: Array(T arr[], ints); Voidprint(); };
Template<typenameT> Array<T>::Array(T arr[], ints) { Ptr = newT[s]; Size = s; For(inti = 0; i< size; i++) Ptr[i] = arr[i]; }
Template<typenameT> VoidArray<T>::print() { For(inti = 0; i< size; i++) Cout<< " "<< *(ptr + i); Cout<<endl; }
Intmain() { Intarr[5] = { 1, 2, 3, 4, 5 }; Array<int> a(arr, 5); a.print(); Return0; } |
Output:
1 2 3 4 5
Working with multi-type Generics:
We can pass more than one data types as arguments to templates. The following example demonstrates the same.
#include <iostream> Usingnamespacestd;
Template<classT, classU> ClassA { T x; U y;
Public: A() { Cout<< "Constructor Called"<<endl; } };
Intmain() { A<char, char> a; A<int, double> b; Return0; } |
Output:
Constructor Called
Constructor Called