copyright hannu laine c++-programming part 4: operator overloading
TRANSCRIPT
Copyright Hannu Laine
C++-programming
Part 4: Operator overloading
HL 2
Main criteria is that member function should be easy to use (most natural and easy way to call it).
We use Point class (discussed earlier) as an example.
The class definition is now
class Point {public:
Point(float x0=0.0, float y0=0.0);//What is the prototype of add likevoid print(const char *explanation) const;
private:float x;float y;
} ;
//Application
void main(void) {Point p1(1.0, 1.0), displacement(10.0, 10.0), p2;//How to add displacement to p1 to get p2;p2.print("The destination is ");
}
Remark. Remember that we have two different views: 1) Application programmer’s view and 2) Component programmer’s view.
Using member function vs. Implementing member function
HL 3
Main criteria is that member function should be easy to use (most natural and easy way to call it).
Using member function vs. Implementing member function
Example. How to add a displacement (x, y) to a coordinate point to get a new location: Point p1, destination, p2;
Way 1: add(p1, displacement, &p2); Way 2: p2 = add(p1,displacement);Way 3: p2.add(p1, displacement); // C++Way 4: p2 = p1.add(displacement); //C++Way 5: displacement.add(p1, &p2); //C++Way 6: p1.add(displacement, &p2); //C++Way 7: p2 = p1 + displacement; //C++...
Which way is the most natural and easy to understand?When the way to call the function is decided you can derive the function prototype.For example, if you prefer way 4, the function prototype should be:
Point Point::add(const Point &displacement) const;
HL 4
Often the way 7 is regarded as the best way. To make it work operator overloading of C++ is needed. Basically the implementations of way 4 and way 7 are very similar. The only difference is that it is possible to make the compiler generate a function call using operator (+ operator is this case).
Way 7 can be made working in the following way:
Function prototype inside the class definition is:
Point operator+(const Point &displacement) const;
The implementation of that function is:
Point Point::operator+( const Point &displacement) const { Point destination;
destination.x = x + displacement.x;destination.y = y + displacement.y;return destination;
}
Operator overloading
compare to function add
The operator function can be used in the following way:destination = origin + displacement;
The interpretation of the compiler is
destination = origin.operator+(displacement);
Almost all operators can be overloaded. For example
Arithmetic operators : +, -, *, /Relational operators : < , <=, ==, !=, >, >= Assignment operator = and indexing operator [ ]
HL 5
We saw how to implement operator + as a member function. It is not always possible to use member function in the operator overloading. Example 1.
Output operator << is overloaded for all build-intypes as a member function of the class ostream,because the expression
int a = 1;cout << a
can be interpreted ascout.operator<<(a);
If we want to overload output operator for our ownclass like Point it is not possible to define it as amember function because we want to use it asfollows
Point p;cout << p;
This means that it must be the member function ofclass ostream. But ostream is fully tested libraryclass. It is not wise to modify it!
Example 2. In this second example we have an object of classCounter. Operator + that adds integer to thecounter can be overloaded as a member function
and used as followsc + 1 (member function )
If we want to add counter to integer in the followingway, it is not possible with a member function
1 + c (this is possible only using friend function).
Operator overloading as friend function
HL 6
The idea of friend function is first illustrated using simplified example:
class C {friend void f(C &c);public:
void member_func();private:
int data_mem;};void main(void) {
C c;f(c); // OK//c.data_mem = 10; syntax error, because
// data_mem is private//c.f(c); syntax error, because f is not a memberc.member_func(); //OK
}void f(C &c) {
c.data_mem = 10; //is allowed because f is } // a friend of C
Operator overloading as friend function
Note that friend function is not a member. This means that keyword public or private means nothing for friend function. It also means there is no “target” object in the call.
HL 7
And now back to the example (+ operator for Point).
Function prototype inside the class definition is:
friend Point operator+(const Point &p,
const Point &displacement);
The implementation of that function is:
Point operator+(const Point &p,
const Point &displacement) {Point destination;destination.x = p.x + displacement.x;destination.y = p.y + displacement.y;return destination;
}
Operator overloading as friend function
The operator function still can be used in the following way:
destination = origin + displacement;
The interpretation of compiler is
destination = operator+(origin, displacement);
HL 8
Complete example 1/2
#include <iostream.h>//class definitionsclass Point {
// friend Point operator+(const Point &p, // const Point &displacement);public:
Point(float x0=0.0, float y0=0.0);void read(const char *prompt );void print(const char *explanation) const;Point operator+(const Point &displacement) const;
private:float x;float y;
} ;//Applicationvoid main(void) {
Point p1(1.0, 1.0), displacement, p2;displacement.read("Enter displacement delta x and y\n”);p2 = p1 + displacement;p2.print("The destination is ");
}
//Operation function implementationsPoint::Point(float x0, float y0) {
x = x0;y = y0;
}
HL 9
Complete example 2/2
void Point::read(const char *prompt) {cout << prompt;cout << "Enter x: ";cin >> x;cout << "Enter y : ";cin >> y;
}
void Point::print(const char *explanation) const{cout << explanation;cout << "(" << x << ", " << y << ")";
}
//Option 1: Member function of the classPoint Point::operator+(const Point &displacement) const{
Point destination;destination.x = x + displacement.x;destination.y = y + displacement.y;return destination;
}
//Option 2: Friend function of the class/* Point operator+(const Point &p,
const Point &displacement) {Point destination;destination.x = p.x + displacement.x;destination.y = p.y + displacement.y;return destination;
} */
HL 10
All objects should be made assignable.
Assignment is defined for all objects by default (compiler generates default assignment).
Default assignment is not satisfactory, if classes have dynamic data members. The problem is actually the same as we learned why copy constructors are needed.
We still use the following Person class as an example with the following definitions and implementations.
//Class definitionclass Person { public: Person(const char *name0="", int age0=0); ~Person(); void print(); private: char *name; int age;};
// Implementations of constructor and destructorPerson::Person(const char *name0, int age0){
name = new char[strlen(name0) + 1];strcpy(name,name0);
age = age0;}
Person::~Person(){delete name;
}
Overloading assignment operator 1/3
HL 11
The following example shows the problems that arise when default assignment is used:
Example. (Class Person has name as a dynamic member.)
void main(void) {
Person p1(“Matti”, 20);
{ Person p2;
p2 = p1; //Problem 1: Memory leak !
p2.setName(“XXX”);
p1.print(); //Problem 2: Name of p1 is XXX !
}
p1.print(); // Problem 3: Name is undefined !
} // Problem 4: Double deletion !
The default assignment is the reason for problems in the example program above.
To fix the problem (to make the class fail safe) we have to write our own assignment operator.
See the next page.
Overloading assignment operator 2/3
HL 12
How to do it for class Person
1) Prototype inside the class definition
const Person& operator=(const Person &p);
2) Implementation
const Person& Person::operator=(const Person &p){
if (this != &p) {//avoid damages in self assignment
delete name;
name = new[strlen(p.name) + 1];
strcpy(name, p.name);
age = p.age;
}
return *this;
}
3) How it is interpreted?
Person p1(“Matti”, 20), p2;
p2 = p1;
The compiler interprets this as
p2.operator=(p1);
4) Why this function returns const reference to the target?
Overloading assignment operator 3/3
HL 13
The input operator >> and output operator << can be overloaded for our own classes, so that it is easy to read them from keyboard and display them on the display.
How should the programmer interpret the following notations:
int a; doudle b; char c[3];
cin >> a; // interpreted as cin.operator<<(a);
cin >> b; // interpreted as cin.operator<<(b);
cin >> c; // interpreted as cin.operator<<(c);
cin >> a >> b; // interpreted as
// ( cin.operator<<(a) ). operator>>(b)
Why these notations are possible? Note that operator overloading, function overloading and reference parameter concepts are all needed here.
How to make the same notations possible for our own classes?
Why do we need to use friend function in this case?
See an example program on the next page.
Overloading input/output operators
HL 14
Overloading << and >> for user defined classes#include <iostream.h>class Point { //class definition
friend istream &operator>>(istream &in, Point &p);friend ostream &operator<<(ostream &out, const Point &p);public:
Point(float x0=0.0, float y0=0.0);private:
float x;float y;
} ;//Applicationvoid main(void) {
Point p;cout << ”Enter point ”;cin >> p;cout << ”The point is ”<< p;
}//Operation function implementationsPoint::Point(float x0, float y0) {
x = x0; y = y0;}//Friend functionsistream &operator>>(istream &in, Point &p) {
cout << ”Enter x and y ”;in >> p.x >> p.y;return in;
}ostream &operator<<(ostream &out, const Point &p) {
out << ”(” << p.x << ”,” << p.y << ”)”;return out;
}
HL 15
All operators so far have been binary operators.
This means that they take two operands.
There are also unary operators that have only one operand. Operator can precede or follows it’s operand.
Some examples of unary operators are:
! (not), ~ (complement), ++ (pre- and post-increment)
The unary operators can be overloaded too.
Now we overload unary operator ++ as a pre-increment operator and as a post-increment operator.
Before that, let’s recall the semantics of these operators using a simple example:
int main(void) {
int i = 0;
cout << i++; // output is 0
cout << ++i; // output is 2
}
In this example the operation where i is used is output (display) operation. So, in the case of post-increment the I is first displayed and after that incremented. In the case of pre-increment the I is first incremented and after that displayed.
Overloading unary operators 1/4
HL 16
Now we want to overload these operators in our own class Counter.
The definition of the class Counter is
class Counter {
friend ostream& operator<<(ostream &out,
const Counter&c)
public:
Counter(int c0 = 0);
Counter& operator++(); // pre-increment
Counter operator++(int); // post-increment
private:
int count;
};
Overloading unary operators 2/4
HL 17
Pre-increment operator:
Prototype inside the class definition:
Counter& operator++();
How to use it?
Counter counter;
++ counter;
How does compiler interpret it?
counter.operator++();
The basic idea is that compiler calls member function operator++ without parameters.
How to implement it?
Counter& Counter::operator++() {
count++:
return *this;
}
Overloading unary operators 3/4
HL 18
Post-increment operator:
Prototype inside the class definition
Counter operator++(int);
How to use it?
Counter counter;
counter++;
How does compiler interpret it?
counter.operator++(0);
The basic idea is that compiler calls member function operator++ with integer parameter.
This parameter is so called dummy parameter and it is used only to make difference between pre increment and post increment operator.
How to implement it?
Counter Counter::operator++(int) {
Counter old = *this;
count++:
return old;
}
Overloading unary operators 4/4
HL 19
1. Conversion from other type to class object:
Constructor can be so called conversion constructor, that works as a conversion operator. For example the constructor Counter (int n0 = 0) of class Counter can be used as a conversion (type casting) operator int -> Counter as follows:
Counter c = 10; //is like Counter c(10); initialize
c = 20; //is like c = Counter(20); conversion and
// assignment
2. Conversion from class object to some other type:
We have to define a type cast operator. For example, to do conversion Counter -> int we need operator int
Prototype inside the class definition:
operator int();
How to use it?Counter c;int i; i = c; // interpreted as i = c.operator int();
It is interesting to study why and how the following program fragment works (see the complete program in handouts). Now we assume that only the first conversion above (constructor) is defined.
Counter i;for (i = 0; i < 5 ; i++)cout << i;
Conversion operators
HL 20
The goal of this example is to
give an additional example of operator overloading,
give an example of overloading indexing operator,
to demonstrate the returning of reference,
to give a foundation for understanding containers in STL.
Disadvantages of arrays in C
Accidental over indexing is possible (index out of range).
Size is fixed (increasing the size requires many user operations (allocate, copy, release).
Assignment is not possible.
Comparison is not possible.
We want to develop a better array that can be used like C-like arrays (without the problems).
This is possible by creating an array class.
Before going to the class definition we learn the concept of returning a reference.
Introduction to intelligent arrays
21
Returning a reference (again)Function can return a value1. Function f1 has a prototype int f1();
This function returns a value (of integer).This value can be used only as “Right value”.int a;a = f1(); //OK right valuef1() = 10; //NOT OK left value
Function can return a pointer2. Function f2 has a prototype int *f2();
This function returns a pointer to integer.This pointer can be used (by referencing) to get the value of int (Right value of int) or to set or modify the value of int (Left value).int a;a = *f2(); //OK right value*f2() = 10; //OK left value
Function can return a reference.3. Function f3 has a prototype int &f3();
This function returns a reference to integer.This reference can be used directly to get the value of int (Right value of int) or to set or modify the value of int (Left value).int a;a = f2(); //OK right valuef2() = 10; //OK left value
Example where comparison is made between returning a reference and returning a pointer:
HL 22
// Class IntelligentArray#include <iostream.h>inline int minimum(int a,int b) { return a<b ? a : b; }
//Header of class intelligent arrayclass IntelligentArray { private:
float *array;int capacity; //size of allocated space
public:IntelligentArray(int initial_size=5, float
init_value=0.0);int size() const;void resize(int new_size);float &operator[]( int i);
};
//Main programint main(void) { IntelligentArray array(5, 0);//initial size 5, all elements to 0 int i; cout << "\n The size is " << array.size(); for ( i = 0 ; i < 12 ; i++) {
if ( i >= array.size()) {array.resize(array.size() + 5);cout << "Size is incremented by 5" << endl;
} array[i] = i; // interpreted as array.operator[ ] (i) = i; } cout << "\n The size is " << array.size() << endl; for (i = 0; i < 12 ; i++ ) cout << array[i] << endl; return 0;}
”Intelligent” array (1)
HL 23
//Implementation of intelligent array
IntelligentArray::IntelligentArray(int initial_size, float init_value) {
capacity = initial_size; array = new float[initial_size]; for (int i = 0 ; i < initial_size ; i++) array[i] = init_value;}
int IntelligentArray::size()const{ return capacity;}
void IntelligentArray::resize(int new_size){int min, i;float *new_array;min = minimum(capacity, new_size);new_array = new float[new_size];for (i = 0 ; i < min ; i++)
new_array[i] = array[i];if (new_size > capacity)
for ( i = capacity ; i < new_size ; i++)new_array[i] = 0.0;
delete array;array = new_array;capacity = new_size;
}
float& IntelligentArray::operator[](int i) {if (0<=i && i < capacity)
return array[i];cout << "Exception handling is needed here";
”Intelligent” array (2)
HL 24
Operator Associativity
() [] -> . Left to right
! ~ + - ++ -- & * (type) Right to left (unary)
* / % Right to left
+ - Left to right
<< >> Left to right
< <= > >= Left to right
== != Left to right
& Left to right (bit-wise
and)
^ Left to right (bit-wise
xor)
| Left to right (bit-wise
or)
&& Left to right (logical
and)
|| Left to right (logical or)
? : (condition) Right to left
= *= /= %= += -= &= ^= |= <<= >>=
Right to left
, Left to right
Operator precedence