Vous êtes sur la page 1sur 37

Lecture 5

Operator Overloading &


Exception Handling

TCP1201 OOPDS

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 wrote multiple catch blocks and
exception matching

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.

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; }
};
Rational multiply (Rational& r1, Rational& r2) {
int n = r1.getNum() * r2.getNum();
int d = r1.getDen() * r2.getDen();
return Rational (n, d);
}

Operator Overloading Advantage

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.

int main() {
Rational r1(1,2), r2(1,3), r3(5,6), r4;
r4 = multiply(r1, multiply(r2,r3));
...

Complex

If we overload multiply operator '*', we can write:

int main() {
Rational r1(1,2), r2(1,3), r3(5,6), r4;
r4 = r1 * r2 * r3;
...

Simple &
easy to
understand

C++ Operators
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
Some unary operators can be used as both prefix and postfix
operators, e.g. increment ++ and decrement -- operators.

prefix

int a = b = 0;
++a;
b = ++a;
cout << "a is:" << a;
cout << "b is:" << b;

postfix

b = a++;

//
//
//
//

a
b
a
b

= 1
= 2, a = 2
is 2
is 2

//
//
cout << "a is:" << a; //
cout << "b is:" << b; //

b
a
a
b

= 2
= 3
is 3
is 2

How to Overload Operators?


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.
returntype operatorS (parameters) {
...
return something;
}
// For our Rational number multiplication.
returntype operator* (parameters) {
...
return something;
8
}

How to Overload Operators?


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.
// For our Rational number multiplication.
Rational operator* (Rational, Rational) {
...
return Rational(); // return a new object.
9
}

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
}

Non-friend
function call
methods.
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
10

friend Access Privilege


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.
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);
}
11

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
}

Friend function
can access private
member directly.
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
12

Overloading Operator '<<' and '>>'


Both insertion operator '<<' and extraction 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.
istream& operator>> (istream& is, <YourClass>& p) {
is >> <code for cin>;
return is; // Return existing object instead of new object.
}
ostream& operator<< (ostream& os, <YourClass>& p) {
os << <code for cout>;
return os; // Return existing object instead of new object.
}
13

Overloading Operator '<<' and '>>'


Overloading '<<' and '>>' allows us to use them on
object of user-defined class.
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;
}
ostream& operator<< (ostream& os,
Rational& r) {
os << r.getNum() << "/" << r.getDen();
return os;
}

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
14

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

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:
Rational
operator++(Rational &) // prefix
The function prototype for overloading postfix
Rational operator++(Rational &, int) // postfix
operator:
Postfix requires 1 int parameter to differentiate
16
itself from prefix.

Overloading Operator '<'


Binary operator '<' is for comparing 2 arguments.
Hence it should return a Boolean result.
class Rational {
...
};
bool operator< (Rational& r1, Rational& r2) {
return r1.getNum()*r2.getDen() < r1.getDen()*r2.getNum();
}
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
17

Overloading Operator '()'


To sort an array or vector of your class by different
attribute at different time.
#include <algorithm> // sort()
...
class Point {
int x, y;
public:
Point (int x = 0, int y = 0) : x(x), y(y) { }
int getX() { return x; }
int getY() { return y; }
};
ostream& operator<< (ostream& os, Point& p) {
os << "(" << p.getX() << ", " << p.getY() << ")";
return os;
}

class SortByX {
public:
bool operator()(Point p1, Point p2)
{ return p1.getX() < p2.getX(); }
};
class SortByY {
public:
bool operator() (Point p1, Point p2)
{ return p1.getY() < p2.getY(); }
};

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;
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] << " ";
}
Output:
(3, 6) (5, 4) (1, 2)
(1, 2) (3, 6) (5, 4)
(1, 2) (5, 4) (3, 6)
18

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

Exception Example: Division By Zero


How to deal with the error below?
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);
}

20

Exception Example: Division By Zero


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? No direct indication from
if/else keywords alone.
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);
}

21

Exception Handling
C++ implements exception handling using try, throw
and catch block.
try block:
Write the code that might generate runtime error within
the try block.
try {
// Code that may generate exceptions.
...
if (<error condition is true>)
throw <Exception object>;
...
}
catch (<Exception type>) {
// Error handling code.
...
22
}

try, throw, and catch blocks


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 {
// Code that may generate exceptions.
...
if (<error condition is true>)
throw <Exception object>; // Jump to catch block.
... // Will be skipped if throw statement is executed.
}
catch (<Exception type>) {
// Error handling code.
...
}
23

try, throw, and catch blocks


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 {
// Code that may generate exceptions.
...
if (<error condition is true>)
throw <Exception object>;
...
}
// No code here.
catch (<Exception type>) { // Thrown exception object must
// match caught exception type.
// Error handling code.
...
}
24

Example: try, throw, and catch blocks


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

25

Example: try, throw, and catch blocks


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.
26

Example: try, throw, and catch blocks


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

The type of the object


being thrown must
match the type of the
parameter in the
catch block

27

Example: try, throw, and catch blocks


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";
}
}

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

Example: try, throw, and catch blocks


Note that exception handling does not require a function to
work.
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";
}
}

Output1:No exception
1 2
Result = 0.5
Output2:With exception
1 0
Cannot divide by zero

29

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

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

Multiple catch Blocks


Sometimes, we might have many different exceptions
for a small block of code.
try {
...
if (<Error1>) throw <Object of exception type1>;
if (<Error2>) throw <Object of exception type2>;
if (<Error3>) throw <Object of exception type3>;
...
}
catch (<Exception type1>) {
// Code that resolves a type1 exception.
}
catch (<Exception type2>) {
// Code that resolves a type2 exception.
}
catch (<Exception type3>) {
// Code that resolves a typeN exception.
}
32

Multiple catch Blocks


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.

33

Multiple catch Blocks


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;
}
}

int main () {
func (1);
func (3);
func (4);
}
Output:
Catch int 11
Catch double 3.5
n is not 1 or 3

No implicit
conversion of
exception type in
catch argument
34

Exception Matching
To catch every possible exception type, use ellipsis "".
try {
...
}
catch (...) { // catches ALL exception types.
...
}

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.
35

Exception Matching
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";
}
}

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

36

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.

37

Vous aimerez peut-être aussi