Headlines News :
Home » , , » Learn C++ in 12 Days - Day 6

Learn C++ in 12 Days - Day 6

Written By Ente Malayalam on Friday, May 24, 2013 | 5/24/2013

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
Share this article :

0 comments:

Post a Comment

 
Support : Creating Website | Johny Template | Maskolis | Johny Portal | Johny Magazine | Johny News | Johny Demosite
Copyright © 2011. Education World - All Rights Reserved
Template Modify by Creating Website Inspired Wordpress Hack
Proudly powered by Blogger