DAY – 6
In yesterday’s class we
have seen that new when fails returns a run time error. You might have noticed
what happens in our system when different types of error occurs right. when a
compile time error occurs, compiler explicitly tells us the area where the
error is occurring which makes things easier for us. In the sense, debugging
becomes easier for us
But
what happens in case of run time errors ? in case of run time errors like
division by zero, stack overflow, deleting a memory which is not allocated for
you etc, the program simply do an abnormal program termination which results in
display of a string “Aborted”. Internally what happens is , whenever a run time
error occurs, the OS invokes the standard library function terminate() which in
turn invokes another standard library function abort() which is printing us the
string aborted. The major problems with this type of run time error handling is
debugging becomes difficult for the user and at the same time the resourced
tied up in the program cannot be managed properly.
For
eg: consider the below program.
//Memory.h
#include
<iostream>
using
namespace std;
int*
alloc_mem();
void
free_mem(int *);
//Memory_Lib.cpp
#include
"mem_header.h"
int*
alloc_mem() {
int *temp;
temp = new (nothrow) int[600000000];
if(temp == NULL) {
cout << "Memory
allocation failed";
exit(1);
}
else
return temp;
}
void
free_mem(int *temp) {
if(temp != NULL)
delete [] temp;
}
//Memory_app.cpp
#include
"mem_header.h"
int
main() {
int *p = NULL;
int *q = NULL;
int *r = NULL;
p = alloc_mem();
q = alloc_mem();
r = alloc_mem();
cout << "back in main"
<< endl;
free_mem(p);
free_mem(q);
free_mem(r);
}
Assume that here memory allocation of p and q
succeeded and r failed. In this case, the program exits from the alloc_mem()
function without returning back to main. ultimately this will result in non freed memories of p and q or I should say
it will result in memory leakage.
Keeping all these things in mind, a better way of
run time error handling was introduced in C++ and is termed as Exception
handling.
Exception
handling:
Exception
handling is achieved using two blocks and one statement in C++. The blocks we
are going to use are try and catch and statement we are going to use is throw.
The syntax is
try {
//statements
to be monitored for errors
}
catch(type 1 arg) {
//processing
}
catch(type 2 arg) {
//processing
}
catch(type 2 arg) {
//processing
}
In a program, if ever you feel that there is a group
of statement which is going to throw you a run time error, put those statements
inside your try block. When ever a run time error occurs in the try block, the
error is “thrown” to catch block using throw statement and catch block takes
care of error processing. The throw statement can be either in the try block or
it can be even inside a function invoked inside try block.
Talking about the syntax a little
more, immediately after a try block, you need to have a minimum of one catch
block. A try can have more than one catch block. Which catch block is invoked
is purely based on the type of the error
thrown. If ever the thrown error doesn’t match with any of the catch block, the
usual procedure will follow. Ie) OS will invoke a standard library function
unexpected(), which inturn invokes terminate() and abort() which will print us
aborted.
With these concepts, lets try to
modify our above program using exception handling.
//Memory_Header.h
#include
<iostream>
using
namespace std;
int*
alloc_mem();
void
free_mem(int *);
//Memory_Lib.cpp
#include
"mem_header.h"
int*
alloc_mem() {
int *temp;
temp = new (nothrow) int[600000000];
if(temp == NULL)
throw "no memory";
else
return temp;
}
void
free_mem(int *temp) {
if(temp != NULL)
delete [] temp;
}
//Memory_App.cpp
#include
"mem_header.h"
int
main() {
int *p = NULL;
int *q = NULL;
int *r = NULL;
try
{
p = alloc_mem();
q = alloc_mem();
r = alloc_mem();
}
catch(const char *s) {
cout << s << endl;
free_mem(p);
free_mem(q);
free_mem(r);
}
}
Now
assume that the person who is developing the library layer wants to another
level of exception handling there. Ie)
#include
"mem_header.h"
int*
alloc_mem() {
int *temp;
try
{
temp = new (nothrow)
int[200000];
if(temp == NULL)
throw "no
memory";
else
return temp;
}
catch(const char *s) {
cout << s << endl;
}
}
Lets say memory allocation failed when trying to
allocate for r. So after executing the catch block, the program will be exiting
from the library layer itself. So what happens is since the control is not gong
to the application layer the memory allocated for p and q will be again leaked.
So after the error handling in library layer, the control should move back to
application layer. For this provision, we have something known as “rethrowing”
in C++. For this you simply have to give a “throw” statement in the catch
block. So the same error caught by that catch block will be rethrown to the
next successive catch.
But again there is
another problem. What is the guarantee that the type of error thrown by the
library layer matches with catch block in the application layer. Not necessary
right. so if there is a catch block in application layer which can capture any type
of error rethrown, it will be better right. such a facility is also there in
C++ and it is known as generic catch. Syntax for that is
catch(…)
{
}
Including
all these things in the above program
//Memory_header.h
#include
<iostream>
using
namespace std;
int*
alloc_mem();
void
free_mem(int *);
//Memory_library.cpp
#include
"mem_header.h"
int*
alloc_mem() {
int *temp;
try
{
temp = new (nothrow)
int[200000];
if(temp == NULL)
throw "no
memory";
else
return temp;
}
catch(const char *s) {
cout << s << endl;
throw;
}
}
void
free_mem(int *temp) {
if(temp != NULL)
delete [] temp;
}
//Memory_application.cpp
#include
"mem_header.h"
int
main() {
int *p, *q, *r;
try
{
p = alloc_mem();
q = alloc_mem();
r = alloc_mem();
}
catch(const char* s) {
free_mem(p);
free_mem(q);
free_mem(r);
}
catch(...) {
free_mem(p);
free_mem(q);
free_mem(r);
}
}
Generic
catch should always be your last catch since we were not be aware of the type
of the error thrown by the user.
Another
thing, if you simply notice the header file, you will never come to know if you
are implementing exception handling in your program or not. So for this, you
need to properly comment your program. A better way of documentation is
available in C++ exception handling and it is using throw list.
Consider
the following program:
//division.h
#include
<iostream>
using
namespace std;
int
divi(int, int) throw (int, float);
//division_libray.cpp
#include
"div.h"
int
divi(int p, int q) throw(int,
float) {
if(q == 0)
throw 'a';
else
return (p / q);
}
//division_application.cpp
#include
"div.h"
int
main() {
int a, b , c;
cout << "Enter a and b"
<< endl;
cin >> a >> b;
try
{
c = divi(a, b);
}
catch(int e) {
cout << "% by
zero" << endl;
}
catch(float e) {
cout << "% by
zero" << endl;
}
}
When a provide a
throw list, it is like restring the function to throw only the type of arguments
specified in the throw list. If the function throws something other than that
specified in the throw list, the same old procedure will occur. Ie) OS will
invoke a standard library function unexpected(), which inturn invokes
terminate() and abort() which will print us aborted.
Another
interesting thing is if you don’t specify anything in the throw list, it is
like restricting the function from throwing anything from the function.
The
same concepts discussed above can be applied to our classes and objects.
Exception classes :
There is a systematic object oriented approach to
handle run time error generated by c++ classes. In addition to the three
keywords we used all these time, we have a new kind of thing to be familiar
with called exception classes.
The
first thing we have to do here is to trace out the type of errors you can
encounter in the class. Accordingly you have to create nested classes which
will act as error classes. When ever a run time error occurs inside some member
function of your class, the object of the error class corresponding to that
error should be thrown.
Back in main, the function calls
which may create run time errors for you should be put inside your try block.
Lets see a small program for another
entity Customer of our car dealer ship system
//customer.h
#include
<iostream>
using
namespace std;
class
Customer {
private:
char Name[20];
char Address[20];
int Reference_No;
public:
class Customer_Name_Check {};
class
Customer_Address_Check {
public:
char Error[50];
Customer_Address_Check(char *e) {
strcpy(Error, e);
}
};
Customer() {}
Customer(char *n, char *a, int
r) {
if(strlen(n) > 20)
throw
Customer_Name_Check();
else
strcpy(Name,
n);
if(strlen(a) > 20)
throw Customer_Address_Check("Memory
insufficient for address");
else
strcpy(Address,
a);
Reference_No = r;
}
};
//Customer_application.cpp
#include
"customer.h"
int
main() {
try
{
Customer Anu("Anushri
Mathur Reddy Prathap", "indiranagar second stage", 12);
}
catch(Customer ::
Customer_Name_Check) {
cout << "Memory not
sufficient for name" << endl;
}
catch(Customer ::
Customer_Address_Check obj) {
cout << obj.Error
<< endl;
}
cout << "Customer
transaction over" << endl;
return 0;
}
Now
since you have seen so many program using exception handling, let me tell you
the greatest advantage of it. Ie) stack unwinding.
Whenever
a throw statement encounters in a function, first it checks if there is catch
handler corresponding to that in its calling function. Before moving to the
calling function it ensures that if ever there is a local object it s memory is
winded up. This process is known as stack unwinding.
We
infact started with run time error handling in new right. lets see how to do
it. Jus like the error classes we have written, there are standard error
classes defined internally for specific operators. For new, there is an
internal error class with the name bad_alloc. When ever a run time error occur
new internally throws an object of this particular error class. As a user all
you need to do is capture it and process it.
See
the following program:
#include
<iostream>
using
namespace std;
int
main() {
int *ptr;
try
{
ptr = new int[600000000];
}
catch(bad_alloc obj) {
cout << obj.what() << endl;
}
}
Reference
: C++ Primer : Lippman
0 comments:
Post a Comment