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 wrote 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() const { return num; }
int getDen() const { return den; }
};
Rational multiply (const Rational& r1, const Rational& r2) {
int n = r1.getNum() * r2.getNum();
int d = r1.getDen() * r2.getDen();
return Rational (n, d);
}
4
5. 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; Simple &
r4 = r1 * r2 * r3; easy to
... understand
5
6. 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.
7. C++ Operators
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
prefix b = ++a; // b = 2, a = 2
cout << "a is:" << a; // a is 2
cout << "b is:" << b; // b is 2
postfix b = a++; // b = 2
// a = 3
cout << "a is:" << a; // a is 3
cout << "b is:" << b; // b is 2
7
8. 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
}
9. 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
10. Parameters & Return Types
• For parameters, use references whenever possible
(especially when the parameter is a big object).
• Always use pass-by-const reference when the argument will
not be modified.
• 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.
• Operators that return a reference to the original object
should return a non-const reference to ensure that the result
can be modified in a later operation., e.g. <<, >>, =, etc.
10
11. Overloading Operator* as Non-Friend Function
class Rational { Binary operator has
int num; 2 parameters.
int den;
public:
int main() {
Rational (int num = 0, int den = 1) Rational r1(1,2),
: num(num), den(den) {} r2(1,3),
void print() const { r3(5,6),
cout << num << "/" << den << endl; r4;
r4 = r1 * r2 * r3;
}
r1.print();
int getNum() const { return num; }
r2.print();
int getDen() const { return den; }
r3.print();
}; r4.print();
Rational operator* (const Rational& r1, }
const Rational& r2) {
int n = r1.getNum() * r2.getNum(); Output:
1/2
int d = r1.getDen() * r2.getDen();
1/3
return Rational (n, d); // Return a new 5/6
// Rational object 5/36
}
11
12. Overloading Operator* as Friend Function
class Rational { Binary operator has 2
friend Rational operator* (const Rational&, parameters.
const Rational&);
int num; int main() {
int den; Rational r1(1,2),
public: r2(1,3),
Rational (int num=0, int den=1) r3(5,6),
: num(num), den(den) {} r4;
r4 = r1 * r2 * r3;
void print() const {
r1.print();
cout << num << "/" r2.print();
<< den << endl; r3.print();
} r4.print();
}; }
Rational operator* (const Rational& r1,
Output:
const Rational& r2) { 1/2
int n = r1.num * r2.num; 1/3
int d = r1.den * r2.den; 5/6
return Rational (n, d); // Return a new 5/36
// Rational object
} 12
14. The Increment Operator (‘++’)
Is a unary operator that can be used as both a prefix ( ++x)
and postfix (x++) operator.
If we have overloaded both prefix and postfix versions of the
increment operator for class Rational:
• A prefix call to operator (++x) will generate a call to:
Rational::operator++() // prefix
• A postfix call to operator (x++) will generate a call to:
Rational::operator++(int) // postfix
Postfix requires 1 int parameter to differentiate itself from
prefix.
14
15. Overloading Operator<
Binary operator has 2 parameters.
class Rational {
...
};
bool operator< (const Rational& r1, const 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 r2n";
else cout << "r1 is NOT smaller than r2n";
if (r1 < r3) cout << "r1 is smaller than r3n";
else cout << "r1 is NOT smaller than r3n";
}
Output:
r1 is smaller than r2
r1 is NOT smaller than r3 15
16. Overloading Operator()
To sort an array or vector of your class by different attribute at
different time.
int main() {
class Point {
Point pts[3] =
int x, y; {Point(3,6),
public: Point(5,4),
Point (int x = 0, int y = 0) : x(x), y(y) { } Point(1,2)};
int getX() const { return x; } for (int i=0; i<3; i++)
cout << pts[i] << " ";
int getY() const { return y; }
cout << endl;
}; sort (pts, pts+3,
ostream& operator<< (ostream& os, const Point& p) { SortByX());
os << "(" << p.getX() << ", " << p.getY() << ")"; for (int i=0; i<3; i++)
return os; cout << pts[i] << " ";
cout << endl;
}
sort (pts, pts+3,
struct SortByX { SortByY());
bool operator() (const Point& p1, const Point& p2) for (int i=0; i<3; i++)
{ return p1.getX() < p2.getX(); } cout << pts[i] << " ";
}; }
struct SortByY {
Output:
bool operator() (const Point& p1, const Point& p2) (3, 6) (5, 4) (1, 2)
{ return p1.getY() < p2.getY(); } (1, 2) (3, 6) (5, 4)
}; (1, 2) (5, 4) (3, 6)
16
17. 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.
17
18. 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);
}
18
19. 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 zeron";
else cout << "Result = " << divide (x, y);
}
19
20. 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.
...
} 20
21. 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.
...
}
21
22. 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.
...
}
22
23. 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);
cout << "Result = " << result; resume execution.
}
catch (double a) { If there is an exception
cout << "Cannot divide by zeron"; of type double,
} catch it.
}
23
24. Example: try, throw, and catch blocks
double divide (double x, double y) { Output1:No exception
if (y == 0) 1 2
throw y; Result = 0.5
return x / y;
}
int main() { Output2:With exception
double x, y; 1 0
cin >> x >> y; Cannot divide by zero
try {
double result = divide (x, y);
cout << "Result = " << result;
}
catch (double a) { When an exception is
cout << "Cannot divide by zeron"; thrown, the codes
} that appear after the
} throw statement in
the try block is
skipped.
24
25. Example: try, throw, and catch blocks
double divide (double x, double y) { Output1:No exception
if (y == 0) 1 2
throw y; Result = 0.5
return x / y;
}
int main() { Output2:With exception
double x, y; 1 0
cin >> x >> y; Cannot divide by zero
try {
double result = divide (x, y);
cout << "Result = " << result;
}
catch (double a) { The type of the object
cout << "Cannot divide by zeron"; being thrown must
} match the type of the
} parameter in the
catch block
25
26. Example: try, throw, and catch blocks
double divide (double x, double y) { Output1:No exception
if (y == 0) 1 2
throw y; Result = 0.5
return x / y;
}
int main() { Output2:With exception
double x, y; 1 0
cin >> x >> y; terminate called after
try { throwing an instance of
double result = divide (x, y); 'double'
cout << "Result = " << result;
}
catch (int a) { If the type of object
cout << "Cannot divide by zeron"; being thrown does
} not match the type of
} the parameter in the
catch block,
26
27. 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; Output1:No exception
double result = x / y; 1 2
cout << "Result = " << result;
Result = 0.5
}
catch (double a) {
cout << "Cannot divide by zeron"; Output2:With exception
} 1 0
} Cannot divide by zero
27
28. 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.
28
29. Example: Exception Propagation
double f2(double x, double y) { Output:With exception
if (y == 0) throw y;
return x / y; 1 0
} Cannot divide by zero
double f1(double x, double y) {
return f2(x, y);
} The exception is
double divide (double x, double y) { propagated in the
return f1(x, y); following order:
} f2(), f1(),
int main() { divide(),
... main().
try {
double result = divide (x, y); The main() catches
cout << "Result = " << result;
and handles the
}
catch (double a) { exception.
...
29
30. 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.
}
30
31. 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.
31
32. Multiple catch Blocks
void func (int n) { int main () {
try { func (1);
if (n == 1) throw 11; // int func (3);
if (n == 3) throw 3.5; // double func (4);
cout << "n is not 1 or 3n"; }
}
catch (double a) { // Won't catch int Output:
cout << "Catch double " << a << endl; Catch int 11
} Catch double 3.5
catch (int a) { // Match int n is not 1 or 3
cout << "Catch int " << a << endl;
}
}
No implicit
conversion of
exception type in
catch argument
32
33. 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.
33
34. Exception Matching
void func (int n) { int main () {
try { func (1);
if (n == 1) throw 11; // int func (2);
if (n == 2) throw string("abc"); func (3);
if (n == 3) throw 3.5; // double func (4);
cout << "n is not 1, 2 or 3n"; }
}
catch (double a) {
cout << "Catch double " << a << endl; Output:
} Not double nor string
catch (string a) { Catch string abc
cout << "Catch string " << a << endl; Catch double 3.5
} n is not 1, 2 or 3
catch (...) { // all types
cout << "Not double nor stringn";
}
}
34
35. 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.
35