1 Overloading Overloading allows a function or operator to have a different meaning depending on the type of objects it is used on. Examples: operator+ can be applied to both integers and strings. When applied to integers, it adds them. When applied to strings it concatenates them. We may wish to have more than one constructor for a class, depending on what inputs are specified by the user
2 Overloading A function with the same name as an existing one but a different number and/or types of parameters is an overloaded instance of the existing function. If two functions have the same name and same number and types of parameters, they cannot have different return types. This would be an error.
3 Overloading Example 1: The maximum() function can be used to find the largest of three or the largest of two integers. int maximum (int x, int y) { return (x>y)? x : y; } int maximum (int x, int y, int z) { if (x < y) return (y < z)? z : y; else return (x < z)? z : x; } int main () { cout << maximum(3,4) << endl; cout << maximum(3,8,-1) << endl; return 0; } screen output: 4 8
4 Overloading Example 2: Consider a Fraction class. There are several places where we can use overloading: An additional constructor that takes as arguments initial numerator and denominator values A plus (+) operator to add two fractions. The output stream operator (<<) to be able to use cout to print a Fraction.
5 Overloading Example 2: Overloading constructors class Fraction { private: int numerator; int denominator; public: Fraction() { numerator = denominator = 1; } Fraction(int num, int den) { numerator = num; denominator = den; } void print() { cout << numerator << "\n--\n" << denominator << endl; }... }; int main () { Fraction f1; Fraction f2(3,4); f1.print(); cout << endl; f2.print(); return 0; } screen output:
6 Overloading Example 2: Overloading constructors We may also "merge" the two constructors into one by using default arguments. Careful! The default arguments must be the last (rightmost ones) class Fraction { private: int numerator; int denominator; public: Fraction(int num=1, int den=1) { numerator = num; denominator = den; } void print() { cout << numerator << "\n--\n" << denominator << endl; }... }; int main () { Fraction f1; Fraction f2(3,4); f1.print(); cout << endl; f2.print(); return 0; } screen output:
7 Overloading Example 3: Overloading the + operator class Fraction { private: int numerator; int denominator; public: Fraction(int num=1, int den=1) { numerator = num; denominator = den; } Fraction operator+ (const Fraction& addend) { Fraction result; result.numerator = numerator * addend.denominator \ + denominator * addend.numerator; result.denominator = denominator * addend.denominator; return result; } void print() { cout << numerator << "\n--\n" << denominator << endl; }... }; int main () { Fraction f1(1,4); Fraction f2(3,4); Fraction sum; sum = f1+f2; sum.print(); return 0; } screen output:
8 Overloading Example 3: Dissecting the overloaded operator+ Fraction operator+ (const Fraction& addend); sum = f1+f2; is equivalent to sum = f1.operator+(f2); Apply operator+ to the current object ( f1 ). Pass the addend, f2, as an argument to the operator. Get back the sum and assign it to a variable.
9 Overloading Example 4: Overloading the << operator We want to be able to type cout << f1; instead of f1.print(); But this is different from the + operator: cout << f1; is equivalent to operator<<(cout, f1) This means that operator<< cannot be a member of the Fraction class (as it is not applied on a Fraction object). The overloaded operator<< will be defined globally, outside the class. Furthermore, it should return a reference to an ostream object in order to allow chained output (e.g. cout << f1 < f2;) Why is this safe?
10 Overloading Example 4: Overloading the << operator class Fraction {... }; ostream& operator<< ( ostream& out, Fraction frac) { out << frac.getNumerator() << "\n---\n" << frac.getDenominator() << endl; return out; } int main () { Fraction f1(3,4); cout << f1; return 0; } screen output:
11 operator= Consider the following chunk of code: The assignment is translated to f1.operator=(f2) A copy of f2 is placed and is assigned to f1 The compiler will perform a member by member assignment This can cause problems when dynamic memory is involved. We must overload the assignment operator, so that it performs a deep assignment. int main () { Fraction f1(3,4); Fraction f2; f2 = f1; return 0; }
12 operator= Example: class ClassInfo { private: int *student_ids; int class_size; public: ClassInfo (int size=0) { if (size == 0) student_ids = NULL; else student_ids = new int[size]; class_size = size; } void read_ids() { for (int i=0; i<class_size; i++) { cout << "Enter next id: "; cin >> student_ids[i]; } }; int main () { ClassInfo eecs231(5); eecs231.read_ids(); ClassInfo eecs211(3); eecs211.read_ids(); eecs211 = eecs231;...
13 operator= and pointers 1:ClassInfo eecs231(16); eecs231.read_ids(); 5 eecs231: : ClassInfo eecs211; 3 eecs211: : eecs211 = eecs231; 5 eecs231: eecs211: 1 163
14 operator= and pointers Not only do both objects point to the same memory, even though they are difference objects, but we also have a memory leak because we lost the original eecs211.student_ids pointer. We must perform a deep assignment!
15 operator= and pointers ClassInfo& operator= (const ClassInfo& rhs) { if ( this == &rhs ) return *this; if ( student_ids != NULL) delete [ ] student_ids; student_ids = new int[rhs.class_size]; class_size = rhs.class_size; return *this; } think of obj1 = obj2; as being equivalent to obj1.operator=(obj2); pass the right-hand-side as a reference but do not allow it to be modified. returning a reference to the current object allows chain assignments: obj1 = obj2 = obj3; the this pointer provides access to an object's own address. this is a C++ keyword.
16 operator= and pointers ClassInfo& operator= (const ClassInfo& rhs) { if ( this == &rhs ) return *this; if ( student_ids != NULL) delete [ ] student_ids; student_ids = new int[rhs.class_size]; class_size = rhs.class_size; return *this; } if the right-hand side and the left-hand side are identical, (e.g. obj1 = obj1) just return a reference to the current object. (What will happen if we don't check this?)
17 operator= and pointers Since the current object will be assigned a new value, make certain that any memory already allocated for it is properly deallocated. ClassInfo& operator= (const ClassInfo& rhs) { if ( this == &rhs ) return *this; if ( student_ids != NULL) delete [ ] student_ids; student_ids = new int[rhs.class_size]; class_size = rhs.class_size; return *this; }
18 operator= and pointers Finally, perform the actual assignment and return a reference to the current object. ClassInfo& operator= (const ClassInfo& rhs) { if ( this == &rhs ) return *this; if ( student_ids != NULL) delete [ ] student_ids; student_ids = new int[rhs.class_size]; class_size = rhs.class_size; return *this; }
19 pointer data members Whenever a class has at least one data member that is a pointer, you MUST write a destructor a copy contructor an overloaded assignment operator THE BIG THREE! If you need one of them, then you also need the other two!
20 friend functions In many cases, we would like a function to have access to private data members of the class, without the function being a member of the class. Examples: operator<< needs to print data member values but is not a class member itself. a function that needs to operate on two or more objects of the same class e.g. a function that takes as arguments two Points and computes the distance between them a function that needs to operate on objects of different classes. e.g. a function HaveCollided() that takes as arguments a Ship and a Torpedo object. Possible Solution: Use a get method (a.k.a. accessor) that returns the values of the class data members Use friends!
21 friend functions A class may allow a function to access its private data members by declaring it as a friend function. Example: class Torpedo; class Ship { private: ShipT type; char *name; Coords position; public:... friend bool HaveCollided(Torpedo&, Ship& ); };... bool HaveCollided(Torpedo& t, Ship& s) {... }
22 friend classes We may also declare a class A to be a friend of class B. This will give A access to the private members of B. IMPORTANT: This does not mean that B has access to the private data members of A. In other words, if A is a friend of B, B is not automatically a friend of A. class CityNetwork { private: City *citylist; Road *highways; public:... }; class City { private: Coords latitude; Coords longitude; public:... friend class CityNetwork; }; Now, CityNetwork can access latitude and longitude
23 evil friends? Friendship may only be granted, not taken. Keep in mind that a friend function is dependent on the implementation of the class that declared it as a friend. If the implementation changes, the function may need to be modified and will certainly need to be recompiled. You must always have very good reasons for using friends. See also:
24 operator<< as a friend class ClassInfo { private: int *student_ids; int class_size; public: ClassInfo (int size=0);.... friend ostream& operator<< (ostream&, const ClassInfo&) }; ClassInfo::ClassInfo (int size) {.... } ostream& operator<< (ostream&, const ClassInfo& obj) {... } classinfo.h classinfo.cpp