At the end of this chapter, you should be able to
Write objective-oriented C++ programs that incorporate errors and exceptions.
Write programs with your own errors and exceptions class to suit particular and individual needs.
Write programs with overloading of operators.
Many of you would have come across a pop-up while using the Internet or operating system, wherein the pop message says “OS or Browser has experienced a serious problem and needs to be closed down. Error reporting is in process”. Indeed, error has occurred. Despite elaborate testing prior to delivery, errors cannot be prevented but they can be minimized. In this chapter, we will study of mechanism of C++ to handle errors and exceptions.
An important feature of any modern OOPs language is overloading. Overloading means making a function or operator perform more than one task, depending on the arguments supplied, at run-time. Function overloading is one such example. C++ has overloaded the operators too. Intrinsic operators have been overloaded thus enhancing the efficiency of language.
There are three types of errors that can crop up in a program:
Exceptions, on the other hand, are unusual conditions that occur at run-time and can lead to errors that can crash a program. The exceptions can be classified as
Synchronous, i.e. those can be predicted. For example, array index going outside the permissible values can be easily detected because such errors occur only when you have executed the statement involving array index. The synchronous exception can occur at any one or more of the following situations:
Asynchronous exceptions, those that cannot be predicted.
Generally, the resources required for the program are allocated at the very beginning and resources are demanded at run-time. Thus, it is likely that our program may exceed the initial allotment. As an example, consider allocated memory for an array. When such an exception occurs, we need a mechanism to carry such information to the area where recourses are allocated so that we can take corrective actions and prevent program crashing.
C++ raises an exception object. For it to do so, we need to use a try block, wherever we expect likely exception. Catch blocks that follow try blocks catch the exception object and take remedial action.
Consider the case of allocating dynamic memory allocation for a two-dimensional matrix. Try will allocate dynamic memory. If it fails due to non-availability of memory, then it will throw the exception object. Catch block will catch the exception thrown by try block and take remedial measures. In the example that follows, we will show how to use try and catch blocks for allocation of memory for a two-dimensional matrix.
Example 11.1: Use of Try and Catch Block. Allocation of Memory for a Two-dimensional Matrix
1. try
2. { A=new int * [m];
3. for (int i=0;i<m;i++)
4. A[i]=new int[n];
5. }catch (bad_alloc) { cout<<”
bad allocation”<<endl;exit(1);}
Example 11.2: A Try Block can Catch Multiple Exceptions Like IO Exception, Index Out of Bounds Exception, etc
try
{
// allocation code here
}
catch (bad allocation object){ }
catch ( File IO exception object) { }
Example 11.3: Nesting of Try Block
try
{ // allocation code here
try{// inner try block code here}
catch ( File IO exception object) { }
}
catch (bad allocation object){ }
catch ( File IO exception object) { }
In the following example, we will demonstrate the use of exception handling in the case of divide by zero error that occurs at run-time. The example is that of calculation of density using the formula density = mass/area. The inputs are mass and radius. At run-time, if the radius is entered as zero, the program throws an exception object called DivideByZero();
Note that we have defined a class DivideByZero{} with an empty body so that we can use it as a tool to catch the exception and enter the catch block. In the catch block, we will inform the user and make a decent exit through exit(0) statement.
Example 11.4: dividebyzero.cpp A Program to Demonstrate Exception Class – Divide by Zero
1. #include<iostream> // for using cin and cout for input and output library
2. using namespace std;
3. class DivideByZero{} // exception class used to enter catch
4. template <class T>
5. T FindDensity(T mass , T radius);
6. void main()
7. { double mass, radius, area, density;
8. while (1)
9. { cout << “
Enter mass in Kgs :”;
10. cin>> mass;
11. cout << “
Enter radius < 0 to test divide by zero and exit> :”;
12. cin>> radius;
13. try
14. {if ( radius==0.0)
15. throw DivideByZero();
16. else
17. {area = 4*3.141581 *radius*radius;density = mass/area;}
18. }catch ( DivideByZero)
19. { cout<<”
Dive by zero error has occurred :”<<endl;
20. cout<<”
the parameters are : mass =”<<mass
21. <<” ”<<”radius =”<<radius<<endl;
22. cout<<”
Exiting the programme:”<<endl;
23. exit(0);
24. }
25. cout<<”
the parameters are : mass =”<<mass
26. <<” ”<<”radius =”<<radius<<” ”<<”density =”<<density<<endl;
27. cout<<“
Normal execution of the programme.“<<endl;
28. }
29. } // end of main
/*Output: Enter mass in Kgs :25.00
Enter radius < 0 to test divide by zero and exit> :3.0
the parameters are : mass =25 radius =3 density =0.221049
Normal execution of the programme.
Enter radius < 0 to test divide by zero and exit> :0.0
Dive by zero error has occurred :
the parameters are : mass =45 radius =0
Exiting the programme:*/
Line No. 3: | class DivideByZero{}; When an exception object is thrown, control passes to catch block, wherein corrective mechanisms are in place. To gain entry into catch block, we have declared and defined an exception class inside the main class. Note that this exception class has no body. Its sole purpose is to give entry into catch block. |
Line No. 15: | throw DivideByZero(); is inside a try block. This means that try block throws DivideByZero() object. |
Lines No. 13–18: | are try block and Lines No 18–24 are catch block. |
We will use stack data structure to demonstrate errors and exception features. But before we do that, there is a need to understand the data structure called stack.
Stack is a linear data structure. Data is inserted and extracted based on a technique called Last in first Out(LIFO) concept. As an example think of an activity of students submitting assignments to their teacher. The teacher will receive and pile them in a stack on the table and will start corrections from the top. Therefore, last in assignment gets service first. As you will see, computer systems depend on stack structure to solve several problems. Representation is shown in Figure 11.1.
Stack Operations
As an example, while programming stack operations, we can include two exceptions, namely, stackfullexception and stackemptyexception.
class Stack
{ public: ………………….
…………………..
// Exception classes
class stackfullerror {};
class stackemptyerror {};
};
Try block and its associated catch block can be placed in the main program, where push and pop operations are contemplated. As shown below, try
{
s.push(“Thunder”);
cout<<s.top()<<endl;
s.push(“Anand”);
cout<<s.top()<<endl;
s.push(“Ramesh”);
}
The following programs, 11.6, will highlight these features:
1. #include<iostream>
2. #include<string>
3. using namespace std;
4. const int max_size=3;
5. class Stack
6. { public:
7. Stack(); //default constructor
8. ~Stack(); //destructor
9. Stack& operator=(const Stack&); //Assignment operator overloading
10. int size() const; //returns number of elements in stack
11. int empty() const; //returns 1 if stack is empty
12. int & top(); //returns top of stack
13. void pop(); //pops top of stack
14. void push(const int &); //pushes element on top of stack
15. // exception classes
16. class stackfullerror {};
17. class stackemptyerror {};
18. private:
19. int* stack_array; //dynamic array
20. int tos; //top of stack pointer
21. int stack_size; //size of dynamic array
22. };
23. //default constructor
24. Stack :: Stack()
25. { stack_array = new int[max_size];
26. stack_size = max_size;
27. tos = -1;
28. }
29. //destructor
30. Stack:: ~Stack() {delete [] stack_array;}
31. Stack& Stack :: operator=(const Stack& S) // Assignment Operator
32. { stack_array = new int[S.stack_size];
33. tos = S.tos;
34. stack_size = S.stack_size;
35. for(int i =0 ; i<S.size(); i++)
36. stack_array[i] = S.stack_array[i];
37. return * this; // for chaining purpose
38. }
39. int Stack :: size() const {return tos+1;}
40. int Stack :: empty() const
41. { if(tos == -1)
42. return 1;
43. else return 0;
44. }
45. void Stack:: push(const int & val)
46. {if( tos<stack_size-1 )
47. { tos++;
48. stack_array[tos] = val;
49. }
50. else
51. throw stackfullerror();
52. }
53. void Stack :: pop()
54. { if(tos >= 0)
55. tos--;
56. else
57. throw stackemptyerror();
58. }
59. int& Stack :: top(){return(stack_array[tos]);}
60. void main()
61. { Stack s;
62. //check for stack full exception
63. try
64. {s.push(10);s.push(20); s.push(30);
65. }catch(Stack::stackfullerror)
66. { cout<<”
stack full exception. Cannot be pursued on to stack:”<<endl;
67. cout<<”
Exiting the catch block of stackfull exception ..”<<endl;
68. }
69. //overloaded assignment operator
70. cout<<”
Result of = operator on stack objects s & t ..”<<endl;
71. Stack t;
72. t=s; // assignment Operator
73. cout<<”
Elements of Stack s “<<endl;
74. while(!s.empty())
75. { cout<<s.top()<<endl;s.pop();}
76. cout<<”
Elements of Stack t after = operator”<<endl;
77. while(!t.empty())
78. { cout<<t.top()<<endl; t.pop();}
79. }//end of main
/* Output: Result of = operator on stack objects s & t ..
Elements of Stack s : 30 20 10
Elements of Stack t after = operator 30 20 10*/
Predefined operators such as +, –/, *, etc. are defined and provided by the compiler to work on intrinsic(basic) data types such as int, char, double, etc. We have also discussed at length while discussing the concept of function overloading that if a function can perform more than one task, then we can call it as overloading and it is an efficient way of utilizing scarce resources like primary memory. Operator overloading also improves the efficiency and throughput of the program by conserving the primary memory of C++.
If we can make these predefined operators work on user-defined data types such as classes, we would call this operator overloading. For example, consider a predefined operator +, and its normal operation:
int x=10, y=20;
int z = x + y ; // contents of x and y are added and placed in z
Now consider two complex numbers in polar form of representation:
Polar v1(25.0,53.50); // magnitude and angle theta
Polar v2(5.0, 45.00);
Polar v3;
Operator + overloading means we can write v3 = v1 + v2. In that case, what would compiler do? Exactly what we tell the compiler to do in operator overloading function definition like
Convert both polar form vectors into Cartesian coordinates like r cos t + j sin t.
Add real terms of v1 and v2 and place them in v3.
Add imaginary terms of v1 and v2 and place them in v3.
Convert the resultant v3 into polar form.
For multiplication:
Convert both into polar form.
Multiply magnitude of polar1 & polar2 to get resultant magnitude.
Add angles of polar1 & polar2 to get resultant theta.
Declare resultant magnitude and resultant theta as a result of multiplication.
Let us attempt this interesting problem in our next problem. In this program, we will show how to overload operators + and * to perform operations on objects v1 and v2 so that we can write
v3=v1+v2;
v3=v1*v2;
1. #include<iostream>
2. #include<cmath>
3. using namespace std;
4. class Polar
5. {// public accessory functions
6. public:
7. Polar(){r=0.0;t=0.0;a=0.0;b=0.0;}
8. Polar(double x, double y){a=x;b=y;r=0.0;t=0.0;}
9. double GetA() const{return a;}
10. double GetB() const {return b;}
11. double GetR() const{return r;}
12. double GetT() const {return t;}
13. void ConvertToPolar(); // to convert to polar form
14. void ConvertToCartesian(); // to convert to Cartesian form
15. friend void DisplayPolar(const Polar &v); // to display in cartesian forms
16. friend void DisplayCartesian(const Polar &v);
17. Polar operator+(const Polar &v1);
18. Polar operator*(const Polar &v1);
19. private:
20. double r; //mag
21. double t; //theeta
22. double a; // real
23. double b; // imaginary
24. };
25. void Polar::ConvertToPolar()
26. { double x=0.0;
r=sqrt(a*a + b*b);
x=atan(b/a);
t= (7.0/22.0)*180.0*x; // PI radians equals 180 degrees
27. }
28. void Polar::ConvertToCartesian()
29. { double x=0.0;
x=atan(b/a);
a=r*cos(x);
b=r*sin(x);
30. }
31. Polar Polar::operator+ (const Polar &v)
32. { Polar v3(a+v.GetA(),b+v.GetB()); return v3;}
33. Polar Polar::operator* (const Polar &v)
34. { Polar v3;
35. float x=0.0;
36. v3.r=r*v.GetR();
37. v3.t=t+v.GetT();
38. x = t*(7.0/22.0)*180;
39. v3.a=r*cos(x);
40. v3.b=r*sin(x);
41. return v3;
42. }
43. void DisplayCartesian(const Polar & v)
44. { cout<< «
Vector in cartesian : real = «<<v.a<<» imaginary =»<<v.b<<endl; }
45. void DisplayPolar(const Polar & v)
46. { cout<< “
Vector in polar form etc: magnitude = “<<v.r<<” Angle =”<<v.t<<endl;
47. }
48. void main()
49. { Polar v1(3.0,4.0),v2(3.0,4.0),v3 ,v4;
50. cout<<»
Given Vector V1 :»;
51. DisplayCartesian(v1);
52. cout<<»
Given Vector V2 :»;
53. DisplayCartesian(v2);
54. v3=v1+v2;
55. cout<<”
after v1 + v2 :operator overloading ..”<<endl;
56. DisplayCartesian(v3);
57. v1.ConvertToPolar();
58. v2.ConvertToPolar();
59. DisplayPolar(v1);
60. DisplayPolar(v2);
61. v4=v1*v2;
62. cout<<»
after v1 * v2 :operator overloading ..»<<endl;
63. DisplayPolar(v4);
64. }
/* Given Vector V1 :Vector in cartesian : real = 3 imaginary =4
Given Vector V2 : Vector in cartesian : real = 3 imaginary =4
after v1 + v2 :operator overloading ..
Vector in cartesian : real = 6 imaginary =8
Vector in polar form etc: magnitude = 5 Angle =53.1087
Vector in polar form etc: magnitude = 5 Angle =53.1087
after v1 * v2 :operator overloading ..
Vector in polar form etc: magnitude = 25 Angle =106.217*/
We can write individual functions to display the object, as shown below, through the usage of DisplayPolar() function, but this is cumbersome. Instead, can we simply write cout<<v3 and still expect the same result? Operator << over loading achieves this result.
Example 11.7: opcout.cpp To Demonstrate Overloading of << Operator
1. #include<iostream>
2. #include<cmath> // for maths related functions like sqrt,cos, tan, tan- etc
3. using namespace std;
4. // Declaration of class called Polar
5. class Polar
6. { public: Polar():r(0.0),t(0.0){} // Default constructor
7. Polar( double f , double g):r(f),t(g){} // constructor
8. ~Polar(){} // Default Destructor
9. Polar operator*(const Polar &v1);
10. void Display(ostream & src) const
11. { src<<”Magnitude = “<<r<<” ”<<”Angle theeta =”<<t<<endl;}
12. double GetR() const{return r;}
13. double GetT() const {return t;}
14. private:double r; t ; // magnitude & angle theeta
15. };
16. ostream & operator<<(ostream &src, const Polar v){ v.Display(src); return src;}
17. Polar Polar::operator* (const Polar &v)
18. { Polar v3;
19. float x=0.0;
20. v3.r=r*v.GetR();
21. v3.t=t+v.GetT();
22. return v3;
23. }
24. void main()
25. { Polar v1(5.0,53.0),v2(6.0,28.0),v3; // v1,v2,v3 is the object of Polar class
26. cout<<“
The Given Vectors in Polar form form ….
“<<endl;
27. cout<<“
Polar form vector v1 :“<<v1<<endl;
28. cout<<“
Polar form vector 2 v2 :“<<v2<<endl;
29. // multiply Polar Vectors v1 & v2 and store it in v3 using overloaded operator
30. v3=v1*v2;
31. cout<<“
v3=v1*v2 in Polar Form :“;
32. cout<<v3;
33. }// end of main
34. /*Output: The Given Vectors in Polar form form ….
35. Polar form vector v1 :Magnitude = 5 Angle theeta =53
36. Polar form vector 2 v2 :Magnitude = 6 Angle theeta =28
37. v3=v1*v2 in Polar Form :Magnitude = 30 Angle theeta =81*/
All operators of C++ can be overloaded. We cannot overload the following operators:
By now we appreciate what is meant by overloading. If we overload the assignment operator, we should be able to write in our code A = B where A and B are objects. In C++, we are aware that we can write assignment of several variables like
double x = y = z = 3.141519;
Clearly after assigning the initial assignment as z = 3.141519, the operator must return the reference of what is assigned so that chaining can be continued and compiler can assign the values to y and thence to x. We also discussed that this pointer is always present when a function is invoked by the object and we can conveniently make use of this pointer to return the reference.
Polar & Polar :: operator = ( const Polar & src)
{ r = src.r; t = src.t;
return *this; } // here we are returning ref for chaining explained above
As a second example, look at the problem in Example 11.6 discussed above wherein we have used operator = overloading
9 Stack& operator=(const Stack&); //Assignment operator overloading
31 Stack& Stack :: operator=(const Stack& S) // Assignment Operator
32 { stack_array = new int[S.stack_size];
33 tos = S.tos;
34 stack_size = S.stack_size;
35 for(int i =0 ; i<S.size(); i++)
36 stack_array[i] = S.stack_array[i];
37 return * this; // for chaining purpose
38 }
Line No. 9: | is a prototype declaration |
Line No. 32: | allocates space for new integer array as per S stack size |
Line No. 33: | equates S.stack_size to stck_size |
Lines No. 35–36: | get the array values |
Line No. 37: | returns a reference * this |
If we are using templates, the assignment operator for a class called Stack in Example 11.4 looks like the following:
template <class T>
Stack<T>& Stack<T> :: operator=(const Stack& S)
{ stack_array = new T[S.stack_size];
tos = S.tos;
stack_size = S.stack_size;
for(int i =0 ; i<size; i++)
stack_array[i] = S.stack_array[i];
return *this;}
While end result is the same, C++ treats pre- and post-increment operators differently.
For pre-increment operators, the syntax is: Height & operator ++ (); //pre fix
For post-increment operators, the syntax is: Height & operator ++ (int);// post fix
Note that the argument int shown in case of post-increment operators is a dummy. It is used by compiler just to distinguish from pre-increment operators.
Example 11.8: prepostfix.cpp Demonstrates Operator ++ Overloading.ence
1. #include<iostream>
2. #include<cmath> // for maths related functions like sqrt,cos , tan , tan- etc
3. using namespace std;
4. class Height {
5. public:
6. Height(float h);
7. Height & operator ++ (); //pre fix
8. Height & operator ++ (int); // post fix
9. ~Height(); // Destructor
10. Height(Height const& m); // Copy constructor
11. Height& operator= (Height const& m); // Assignment operator
12. float GetHeight() const { return yourHeight;}
13. private:float yourHeight;
14. };
15. inline Height::Height(float h):yourHeight (h){}
16. inline Height::~Height() {} // Destructor
17. Height & Height::operator++ (){ ++yourHeight;return * this;}
18. Height & Height::operator ++ (int) {yourHeight++;return * this;}
19. void main()
20. { Height Ramesh(179.0),Gautam(172.0),Anand(171); // three objects created
21. cout<<”
Given Height…”<<endl;
22. cout <<Ramesh.GetHeight()<<” : “<<Anand.GetHeight()<<” : “<<Gautam.GetHeight()<<endl;
23. ++Ramesh;++Anand;++Gautam;
24. cout<<”
Height after prefix ++ overloading…”<<endl;
25. cout <<Ramesh.GetHeight()<<” : “<<Anand.GetHeight()<<” : “<<Gautam.GetHeight()<<endl;
26. Ramesh++;Anand++;Gautam++;
27. cout<<”
Height after postfix ++ overloading…”<<endl;
28. cout <<Ramesh.GetHeight()<<” : “<<Anand.GetHeight()<<” : “<<Gautam.GetHeight()<<endl;
29. }
/*Output: Given Height…179 : 171 : 172
Height after prefix ++ overloading…180 : 172 : 173
Height after postfix ++ overloading…181 : 173 : 174*/
Lines No. 17 and 18: | Pre- and post-increment operators definition. Observe that your Height has been increased and *this (reference object) has been returned |
We are aware from our study of dynamic memory allocation that new operator is used for allocation of memory in heap memory. The syntax for new and delete operators is presented here:
int *x = new int; // creation and allocation of heap memory
*x=25; // assign value
Alternately, we can use a single statement
int *x = new int(12); allocate value 12 to pointer variable on heap memory
……………………
delete x; // q pointer has been deleted
For array declarations : //x is a pointer variable pointing to array by name x
int *x =new int[12]; // having 12 contiguous locations.
…………………….
delete [] x; // deletion of pointer to an array
In the example that follows, we will show overloading of new and delete operators. As we are overloading new and delete operations, for memory allocation, we cannot use their original definitions. Instead, we use malloc() and free() functions from C language. The syntax is shown below:
int *x ; // x is a pointer variable
//allocate dynamic memory space using malloc()
x = (int *)malloc( 12 *sizeof(int));
……………………..
free(x); // delete the memory allocated
Example 11.9: Stundent1.cpp A program to Overload New and Delete
1. #include <iostream>
2. #include <cstdlib> // for using malloc() & free operators
3. #include <new> // including new fro c standard library
4. using namespace std;
5. class Stundent
6. {public:
7. Stundent() {rollNo = marks = 0;} //default constructor
8. Stundent(int a, int b) {rollNo = a;marks = b;}
9. void Display()
10. {cout <<”Roll No :”<<rollNo << “ Marks : “<<marks << endl;}
11. int GetrollNo()const { return rollNo;}
12. int Getmarks()const { return marks;}
13. void * operator new(size_t size);
14. void operator delete(void *std);
15. private :
16. int rollNo, marks;
17. };
18. // overloaded new operator
19. void *Stundent::operator new(size_t size)
20. { void *std;
21. cout << “In overloaded new.
”;
22. std = malloc(size); // we are using malloc because we are overloading new
23. if(!std)
24. {bad_alloc e; throw e;}
25. return std;
26. }
27. // delete operator overloaded
28. void Stundent::operator delete(void *std)
29. { cout << “In Side overloaded delete.
”; free(std);}
30. void main()
31. { Stundent *std;
32. try {
33. std = new Stundent (50595, 89);
34. }catch (bad_alloc e) {cout << “Allocation error for obj1.
”;exit(1);}
35. std->Display();
36. delete std; // free an object
37. }
/*Output: In overloaded new.
Roll No :50595 Marks : 89
In Side overloaded delete.*/
There are many occasions wherein an object of a class, say class number, needs to be converted to an object of string class. Conversion operators are useful in this case. It means userdefined objects such as classes and structures, etc., can be type casted to other objects by a facility called conversion operators. Conversion operator has to be a non-static member function (why?). For example:
Student std; // an object of Student
Operator char * () const; // converts std into char *
Operator int() const; // converts std into int
Operator Person() const; // converts std into an object of user-defined class Person ;
Const implies that conversion operator cannot change the original object. Note also that conversion operator does not have any return type. Compiler automatically converts object to char * when it sees the command operator char *(). Note also that overloaded conversion operators can be used to convert user-defined object into fundamental data type.
Example 11.10: //convop.cpp A Program to Show Conversion Operator
1. #include<iostream>
2. #include<cstring>
3. using namespace std;
4. class OwnString
5. { public:
6. OwnString(); //default constructor
7. OwnString(const char *s){stg=s;}; // constructor with argument
8. operator const char *() {return stg;} // conversion operator to convert to ctype string
9. private:
10. const char *stg;
11. int size;
12. };
13. void main()
14. { OwnString stg(“Example of conversion Operator”);
15. int n = strcmp(stg,”Example”);
16. cout<<”Strings are NOT same : “<<n<<endl;
17. n=strcmp(stg,”Example of conversion Operator”);
18. cout<<”Strings are same : “<<n<<endl;
19. }
/*Output
Strings are NOT same : 1 : Strings are same : 0*/
Line No. 7: | is overloaded constructor |
Line No. 8: | conversion operator that returns stg of type const char *(ctype) so that we can use it with strcmp in line no. 15 and 17. |
3.136.18.218