Presentation is loading. Please wait.

Presentation is loading. Please wait.

Operator Overloading & Exception Handling TCP1201 OOPDS 1 Lecture 5 1.

Similar presentations


Presentation on theme: "Operator Overloading & Exception Handling TCP1201 OOPDS 1 Lecture 5 1."— Presentation transcript:

1 Operator Overloading & Exception Handling TCP1201 OOPDS 1 Lecture 5 1

2 Learning Objectives Operator Overloading  To understand what is operator overloading  To understand the advantage of operator overloading  To understand how to overload operators as functions Exception handling  To understand what is exception  To realize the advantages of exception handling  To understand the use of try, throw and catch block  To understand how exception propagation works  To understand how to write multiple catch blocks and exception matching 2

3 Operator Overloading Add capability to an operator via writing a new "special function" for the same operator but with different data types and combinations of parameters. For example, C++'s operator '+': C++ has built-in support for using operator '+' to add int or double, and also for concatenating string. However using operator '+' to add 2 objects of your (user-defined) class is not automatically supported. We can write a "function" to make C++ support adding 2 objects of user-defined class. This process is called operator overloading. 3

4 Operator Overloading Advantage It provides a simpler way for doing some operations on user-defined classes. Consider the following Rational class which represents a rational/fraction number, e.g. 1/2, 2/3: class Rational { int num; // numerator int den; // denominator public: Rational (int num=0, int den=1) : num(num), den(den) {} int getNum() { return num; } int getDen() { return den; } }; 4 Rational multiply (Rational& r1, Rational& r2) { } int n = r1.getNum() * r2.getNum(); int d = r1.getDen() * r2.getDen(); return Rational (n, d);

5 To multiply rational numbers e.g. 1/2 * 1/3 * 5/6 = 5/36, we can use the method multiply(), but it looks more complex. If we overload multiply operator '*', we can write: int main() { Rational r1(1,2), r2(1,3), r3(5,6), r4; r4 = multiply(r1, multiply(r2,r3));... int main() { Rational r1(1,2), r2(1,3), r3(5,6), r4; r4 = r1 * r2 * r3;... Simple & easy to understand Complex Operator Overloading Advantage 5

6 Operators in C++ are divided into 2 categories based on the number of arguments they accept: Unary operators accept one argument x++, --x, !x, etc. Binary operators accept two arguments x+y, x-y, x*y, x<y, x=y, etc. C++ Operators

7 Some unary operators can be used as both prefix and postfix operators, e.g. increment ‘++’ and decrement ‘--’ operators. int a = b = 0; ++a; // a = 1 b = ++a; // b = 2, a = 2 cout << "a is:" << a; // a is 2 cout << "b is:" << b; // b is 2 b = a++; // b = 2 // a = 3 cout << "a is:" << a; // a is 3 cout << "b is:" << b; // b is 2 prefix postfix C++ Operators 7

8 We overload an operator by writing a special function with the keyword operatorS, where S is an operator symbol (+, -, *, /, !, ++, etc.). We should overload an operator in a sensible way and meet general expectation, e.g. don't overload '*' operator to perform division. How to Overload Operators? returntype operatorS (parameters) {... return something; } 8 // For our Rational number multiplication. returntype operator* (parameters) {... return something; }

9 The number of parameters depend whether the overloaded operator is unary or binary. Unary operator has 1 parameter. Binary operator has 2 parameters. Since operator '*' is a binary operator hence the number of parameter is 2. We are multiplying 2 Rational objects and expecting the result is also a Rational object, hence the data type of both parameter and return type should be Rational. How to Overload Operators? 9 // For our Rational number multiplication. Rational operator* (Rational, Rational) {... return Rational(); // return a new object. }

10 Overloading Operator '*' as Non-Friend Function class Rational { int num; int den; public: Rational (int num = 0, int den = 1) : num(num), den(den) {} void print() { cout << num << "/" << den << endl; } int getNum() { return num; } int getDen() { return den; } }; Rational operator* (Rational& r1, Rational& r2) { int n = r1.getNum() * r2.getNum(); int d = r1.getDen() * r2.getDen(); return Rational (n, d); // Return a new // Rational object } int main() { Rational r1(1,2), r2(1,3), r3(5,6), r4; r4 = r1 * r2 * r3; r1.print(); r2.print(); r3.print(); r4.print(); } Output: 1/2 1/3 5/6 5/36 Non-friend function call methods. 10

11 friend Access Privilege class Rational { friend Rational operator* (Rational&, Rational&); int num, den;... }; Rational operator* (Rational& r1, Rational& r2) { int n = r1.num * r2.num; // Directly access private member. int d = r1.den * r2.den; return Rational (n, d); } If class A grants a global function (not a method) or another class B (not subclass) a friend access privilege, it means that class A allows the global function or class B to access its private members. This actually violates encapsulation but is generally accepted when doing operator overloading. A friend function is not a method of the class. 11

12 Overloading Operator '*' as Friend Function class Rational { friend Rational operator* (Rational&, Rational&); int num; int den; public: Rational (int num=0, int den=1) : num(num), den(den) {} void print() { cout << num << "/" << den << endl; } }; Rational operator* (Rational& r1, Rational& r2) { int n = r1.num * r2.num; int d = r1.den * r2.den; return Rational (n, d); // Return a new // Rational object } int main() { Rational r1(1,2), r2(1,3), r3(5,6), r4; r4 = r1 * r2 * r3; r1.print(); r2.print(); r3.print(); r4.print(); } Output: 1/2 1/3 5/6 5/36 Friend function can access private member directly. 12

13 istream& operator>> (istream& is, & p) { is >> ; return is; // Return existing object instead of new object. } ostream& operator & p) { os ; return os; // Return existing object instead of new object. } Overloading Operator ' >' Both insertion operator ' >' are binary operators because they need 2 arguments to work. They usually have the following pattern (return type and first parameter) regardless of the class you want to overload. Your job is to figure out your class and the code for cin or cout. 13

14 class Rational { friend istream& operator>> (istream&, Rational&);... }; istream& operator>> (istream& is, Rational& r) { is >> r.num >> r.den; // r.getNum() & r.getDen() won't work. Why? return is; } int main() { Rational r1, r2, r3; cin >> r1 >> r2; r3 = r1 * r2; cout<< r1<<endl << r2<<endl << r3; } Output: 1 2 3 4 1/2 3/4 3/8 Overloading Operator ' >' Overloading ' >' allows us to use them on object of user-defined class. 14 ostream& operator<< (ostream& os, Rational& r) { os << r.getNum() << "/" << r.getDen(); return os; }

15 Parameters & Return Types For parameters, use references whenever possible (especially when the parameter is a big object). Always try to follow the spirit of the built-in implementations, e.g. comparison operators (==, !=, >, etc) generally return a bool, so an overloaded version should do the same. 15

16 Increment Operator '++' Is a unary operator that can be used as both a prefix ( ++x) and postfix (x++) operator. How does compiler know whether we are overloading prefix or postfix? Use our Rational class as example: The function prototype for overload prefix operator: The function prototype for overloading postfix operator: Postfix requires 1 int parameter to differentiate itself from prefix. Rational operator++(Rational &) // prefix Rational operator++(Rational &, int) // postfix 16

17 class Rational {... }; bool operator< (Rational& r1, Rational& r2) { return r1.getNum()*r2.getDen() < r1.getDen()*r2.getNum(); } Overloading Operator '<' Binary operator '<' is for comparing 2 arguments. Hence it should return a Boolean result. 17 int main() { Rational r1(1,2), r2(2,3), r3(1,2); if (r1 < r2) cout << "r1 is smaller than r2\n"; else cout << "r1 is NOT smaller than r2\n"; if (r1 < r3) cout << "r1 is smaller than r3\n"; else cout << "r1 is NOT smaller than r3\n"; } Output: r1 is smaller than r2 r1 is NOT smaller than r3

18 #include // sort()... class Point { int x, y; public: Point (int x = 0, int y = 0) : x(x), y(y) { } int getX() const { return x; } int getY() const { return y; } }; ostream& operator<< (ostream& os, Point& p) { os << "(" << p.getX() << ", " << p.getY() << ")"; return os; } int main() { Point pts[3] = {Point(3,6), Point(5,4), Point(1,2)}; for (int i=0; i<3; i++) cout << pts[i] << " "; cout << endl; } Overloading Operator '()' To sort an array or vector of your class by different attribute at different time. 18 class SortByX { public: bool operator()(const Point& p1, const Point& p2) { return p1.getX() < p2.getX(); } }; class SortByY { public: bool operator() (const Point& p1, const Point& p2) { return p1.getY() < p2.getY(); } }; sort (pts, pts+3, SortByX()); for (int i=0; i<3; i++) cout << pts[i] << " "; cout << endl; sort (pts, pts+3, SortByY()); for (int i=0; i<3; i++) cout << pts[i] << " "; (1, 2) (5, 4) (3, 6) (1, 2) (3, 6) (5, 4) Output: (3, 6) (5, 4) (1, 2)

19 Exception Handling When a program is executed, unexpected situations may occur. Such situations are called exceptions. In other word: An exception is a runtime error caused by some abnormal conditions. Example: Division by zero Failure of new operator to obtain a requested amount of memory Exception handler is code that handles the exception (runtime error) when it occurs. 19

20 Exception Example: Division By Zero double divide (double x, double y) { return x / y; // divide by 0 if y = 0 } int main() { double x, y; cin >> x >> y; cout << "Result = " << divide (x, y); } How to deal with the error below? 20

21 Exception Example: Division By Zero double divide (double x, double y) { return x / y; // divide by 0 if y = 0 } int main() { double x, y; cin >> x >> y; if (y == 0) cout << "Cannot divide by zero\n"; else cout << "Result = " << divide (x, y); } A solution is shown below. It works but the codes that handles the error mixes with the codes for division, making the codes harder to read (is if for division and else for error handling, or the other way?) 21

22 C++ implements exception handling using try, throw and catch block. try block: Write the code that might generate runtime error within the try block. Exception Handling 22 try { // Code that may generate exceptions.... }

23 throw statement: Use keyword throw in try block to signal that abnormal condition or error has occurred. If the throw statement is executed, the C++ runtime will skip the remaining of the try block, and jump to the catch block to continue execution. try, throw, and catch blocks 23 try { // Code that may generate exceptions.... if ( ) throw ; // Jump to catch block.... // Will be skipped if throw statement is executed. }

24 catch block: Write the code that catches the thrown exception object in catch block. This is the exception handler. Unhandled/Uncaught thrown exception will terminate the program. try, throw, and catch blocks 24 try { // Code that may generate exceptions.... if ( ) throw ; // Jump to catch block.... // Will be skipped if throw statement is executed. } // No code here. catch ( ) { // Thrown exception object must // match caught exception type.... }

25 double divide (double x, double y) { if (y == 0) throw y; return x / y; } int main() { double x, y; cin >> x >> y; try { double result = divide (x, y); cout << "Result = " << result; } catch (double a) { cout << "Cannot divide by zero\n"; } If there is an exception, throw it. Put code that may generate error in try block. If there is no exception, resume execution. If there is an exception of type double, catch it. Example: try, throw, and catch blocks 25

26 double divide (double x, double y) { if (y == 0) throw y; return x / y; } int main() { double x, y; cin >> x >> y; try { double result = divide (x, y); cout << "Result = " << result; } catch (double a) { cout << "Cannot divide by zero\n"; } Output1:No exception 1 2 Result = 0.5 Output2:With exception 1 0 Cannot divide by zero When an exception is thrown, the codes that appear after the throw statement in the try block is skipped. Example: try, throw, and catch blocks 26

27 double divide (double x, double y) { if (y == 0) throw y; return x / y; } int main() { double x, y; cin >> x >> y; try { double result = divide (x, y); cout << "Result = " << result; } catch (double a) { cout << "Cannot divide by zero\n"; } Example: try, throw, and catch blocks Output1:No exception 1 2 Result = 0.5 Output2:With exception 1 0 Cannot divide by zero The type of the object being thrown must match the type of the parameter in the catch block 27

28 double divide (double x, double y) { if (y == 0) throw y; return x / y; } int main() { double x, y; cin >> x >> y; try { double result = divide (x, y); cout << "Result = " << result; } catch (int a) { cout << "Cannot divide by zero\n"; } Example: try, throw, and catch blocks Output1:No exception 1 2 Result = 0.5 Output2:With exception 1 0 terminate called after throwing an instance of 'double' If the type of object being thrown does not match the type of the parameter in the catch block, 28

29 int main() { double x, y; cin >> x >> y; try { if (y == 0) throw y; double result = x / y; cout << "Result = " << result; } catch (double a) { cout << "Cannot divide by zero\n"; } Example: try, throw, and catch blocks Output1:No exception 1 2 Result = 0.5 Output2:With exception 1 0 Cannot divide by zero Note that exception handling does not require a function to work. 29

30 Exception Propagation If the function containing the throw statement does not catch the exception, the exception will be propagated up to the caller of the function until it reaches a try block or the main function. In the former case, the try/catch block of the caller handles the exception if the exception type matches one of the catch block. Otherwise the exception will be propagated up again. If the exception reaches the main function and is not handled, the program will be terminated. 30

31 Example: Exception Propagation double f2(double x, double y) { if (y == 0) throw y; return x / y; } double f1(double x, double y) { return f2(x, y); } double divide (double x, double y) { return f1(x, y); } int main() {... try { double result = divide (x, y); cout << "Result = " << result; } catch (double a) {... Output:With exception 1 0 Cannot divide by zero The exception is propagated in the following order: f2(), f1(), divide(), main(). The main() catches and handles the exception. 31

32 Sometimes, we might have many different exceptions for a small block of code. Multiple catch Blocks 32 try {... if ( ) throw ;... } catch ( ) { // Code that resolves a type1 exception. } catch ( ) { // Code that resolves a type2 exception. } catch ( ) { // Code that resolves a typeN exception. }

33 Sometimes, we might have many different exceptions for a small block of code. Multiple catch Blocks 33 try {... if ( ) throw ;... } catch ( ) { // Code that resolves a type1 exception. } catch ( ) { // Code that resolves a type2 exception. } catch ( ) { // Code that resolves a typeN exception. }

34 Sometimes, we might have many different exceptions for a small block of code. Multiple catch Blocks 34 try {... if ( ) throw ;... } catch ( ) { // Code that resolves a type1 exception. } catch ( ) { // Code that resolves a type2 exception. } catch ( ) { // Code that resolves a typeN exception. }

35 But, which catch block will be instigated/invoked? Depend on the type of exception object. The type must match exactly, no implicit conversion will be done by C++. Type double does not match with type int. Only one catch block will be executed for an exception. The catch block that first matches the exception type would be chosen. Multiple catch Blocks 35

36 int main () { func (1); func (3); func (4); } Output: Catch int 11 Catch double 3.5 n is not 1 or 3 void func (int n) { try { if (n == 1) throw 11; // int if (n == 3) throw 3.5; // double cout << "n is not 1 or 3\n"; } catch (double a) { // Won't catch int cout << "Catch double " << a << endl; } catch (int a) { // Match int cout << "Catch int " << a << endl; } No implicit conversion of exception type in catch argument Multiple catch Blocks 36

37 Exception Matching To catch every possible exception type, use ellipsis "…". Limitations of catch (...): You can't tell what type of exception occurred. No argument to reference. Should always be placed as the last catch block. try {... } catch (...) { // catches ALL exception types.... } 37

38 Exception Matching int main () { func (1); func (2); func (3); func (4); } Output: Not double nor string Catch string abc Catch double 3.5 n is not 1, 2 or 3 void func (int n) { try { if (n == 1) throw 11; // int if (n == 2) throw string("abc"); if (n == 3) throw 3.5; // double cout << "n is not 1, 2 or 3\n"; } catch (double a) { cout << "Catch double " << a << endl; } catch (string a) { cout << "Catch string " << a << endl; } catch (...) { // all types cout << "Not double nor string\n"; } 38

39 Advantages of Exception Handling Using try, throw, and catch blocks to handle exception offer the following advantages: 1.Provide clarify on the section of codes that handle the error. 2.You may throw an exception in a function/method, and handle it somewhere else. 39


Download ppt "Operator Overloading & Exception Handling TCP1201 OOPDS 1 Lecture 5 1."

Similar presentations


Ads by Google