Skip to main content

Operator Overloading -II

Let us consider some special cases of operator overloading

Overloading of subscript operator:
 
Subscript operator ([]) can be overloaded to access the dynamically allocated array elements within an object. Using subscript operator, we can treat these like a POD array and access ith element of the array using obj[i]
 
class Arr
{
 int *arr;
 int size;
public:
 int& operator [](int n) ;
 int operator[](int n) const;
/****code *****/
};

int &Arr::operator [](int index)
{
 return arr[index];
}

int Arr::operator [](int index) const
{
 return arr[index];
}
int main()
{
 Arr obj(10);
 for(int i=0;i<10;i++)
    obj[i] = i*i;
 for(int i=0;i<10;i++)
    cout<<arr[i]<<" ";
}

Output
    0 1 4 9 16 25 36 49 64 81


Why do we have two functions for this operator?  Is it allowed? Is it necessary?

const version of [] operator function is written so that constant objects can also access array elements. Non-constant version is helpful in modifying the array elements. Ideally you can use const function for displaying the elements and non-const version for setting values to these elements.

But we have to make sure that non-const version of [] function returns a reference, so that you can assign values to elements. Remember that a function returning a reference can be assigned a value.

Increment and Decrement operators

Increment and decrement operators have prefix and postfix versions for PODs. a++ and ++a.  So how do we overload these operators with two versions for objects?


The solution is - for postfix versions,  we should use a dummy integer parameter.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class Number
{
 int num;
public:
 Number & operator++();/*prefix version*/
 Number operator++(int a);/*postfix version*/
/****code***/
};
Number & Number::operator++()
{
 num++;
 return *this;
}

Number Number::operator++(int dummy)
{
 Number temp = *this;
 num++;
 return temp;
}

int main()
{
 Number obj(9);
 Number obj2(1000);
 obj2 = ++obj;/*calls prefix*/
 obj = obj2++;/*calls postfix*/
}

In line 26, obj2 is assigned to prefix increment of obj. So obj2 will become 10. In line 27, we are using postfix ++ operator.   

obj2 is  is assigned to obj first and then is incremented. So obj becomes 10 and obj2 will be 11.

 Overloading new operator:


new operator takes size_t parameter (unsigned int) and returns a void pointer. The easy way of overloading new operator would be

void *Number::operator new(size_t bytes)
{
 void *ptr = malloc(nbytes);
 return ptr;
}
void Number::operator delete(void *ptr)
{
 free(ptr);
}
 

Note that compiler provided new operator will be sufficient in most situations. You need to overload new and delete operator, only if you want to use your own block of memory for dynamic allocation.

Insertion and Extraction operators (<< and >>)

These two operators are used to read and write objects from streams.



These should be overloaded as non-member functions  because their first parameter will not be the object of the given class.  

>> must have istream object as first parameter and << must have ostream object as first parameter.

These two operator functions must return reference to iostream objects in order to use chaining in i/o operation. And it is necessary to use second parameter also as reference parameter in extraction operator as the function modifies the object.

class Number
{
 int num;
public: 
 friend ostream & operator <<(ostream& out, Number & obj);
 friend istream & operator >>(istream& in, Number & obj);
/****code***/
};

ostream & operator <<(ostream& out, Number & obj)
{
 out<<obj.num;
 return out;
}

istream & operator >>(istream& in, Number & obj)
{
 in>>obj.num;
 return in;
}

You should ensure that these two member functions are friends if you want them to access data directly.

Once >> is overloaded you can read the object directly using cin and once << is overloaded you can display the object directly using cout.

Number obj1(10);
cout<<obj1;/*displays 10*/
cin>>obj1;/*reads num of obj1*/


Conversion Operators:

These operator functions convert objects to other PODs or other type of objects. They are written without a return type. Instead, return type is the name of the operator. For example, to convert object to int, we  write an overloaded operator with name int and no return type.

class Number
{
 int a;
public:
 operator int()/*Converts Number to int*/
 {
 return a;
 }
/****code*****/
};
int main()
{
 Number obj(100);
 int m = obj;
/* uses int conversion operator */
 cout<<m;
}
 
Function call operator:


parantheses - () can be overloaded to create an object which behaves like a function. Such objects are called function objects or functors.

class Cube
{
public:
int operator ()(int n)
{
 return n*n*n;
}
};
int main()
{
 Cube cobj;
 int m = cobj(10);/*this cubes 10 and returns 1000*/
 cout<<m;
}

Output
1000

Overloading of assignment operator:


Compiler provides overloaded assignment operator function for a class which copies an object to another byte by byte. If your class does not use dynamic memory at all, then compiler provided assignment operator would be sufficient.

But if the class uses dynamic memory you have to write your own assignment operator because you need  a deep copy.

Assignment operator function looks almost like a copy constructor. You allocate memory and you copy elements. But before that, you should release memory allocated earlier for pointer.

There is one more problem however. What if you use a statement which ultimately becomes self assignment because of references as shown below?

Integer obj1(10);
Integer &obj2 = obj1;
obj2= obj1;//self assignment

In this case, if you delete memory, all hell breaks loose.

To avoid this problem, check for self assignment, then delete memory, allocate new memory and copy elements.

class Arr
{
 int *arr;
 int n;
public:
 A& operator =(const A &obj1);
/****code *****/
};
A& A::operator =(const A & obj1)
{
 if(this !=&obj1)/*self assignment?*/
 {
 delete arr;
 arr = new int[obj1.n];
 for(int i=0;i<n;i++)
 arr[i] = obj1.arr[i];
 }
 return *this;
}

Here the operator function checks if there is self assignment by using this !=&obj1

Comments

Popular posts from this blog

Polymorphism

You hear the term Polymorphism too frequently with object oriented languages. Along with Inheritance and Encapsulation, polymorphism is one of the corner stones of object oriented design. What is Polymorphism, exactly? Polymorphism is a mechanism by which you provide single interface for multiple methods. (poly - many, morph - form). In C++, polymorphism can be compile time or run-time. Compile time polymorphism is provided with the help of overloaded operators/functions and templates. Run time polymorphism is provided with the help of virtual functions and late binding. Late Binding: Connecting a function call to function body is called binding. Most functions use early binding where this binding happens before the program is run - during compile time. This is also called static binding. Late binding (also called dynamic binding)  is when a function call is connected to function body at run time. This is done after looking at the type of the object. Late binding is ach

Operator Overloading

What is operator overloading ? Operator overloading is the process of customizing C++ operators for operands of user defined types.   When you have two objects of a class- num1 and num2 , you can write a function to add them such as  ans = add(num1,num2); That does not look neither simple nor intuitive. You would prefer to write      ans = num1+num2; as you would write expressions for basic data types like integers, floats etc.    This can be done using Operator overloading. Operator overloading lets you write such statements. That is, it lets you call your functions on objects using  +, - ,* etc.    + operator will call addition function on the object (when you write op. overloading function for +). * will call multiply on objects etc. Names of overloaded operator functions start with keyword operator followed by  symbol of the operator. e.g. +, - etc. Unary operator functions take 0 parameters for members. The operand for these function is the object cal

Constant members of a Class

A constant  is a value which can not be modified. As in C, we can have literal constants using #define and we can have enums and we can define a variable to be const .  Let us look at const variables here. By declaring a variable as const , we ensure that it is not modified accidentally. Any modification to a constant will give a compilation error. A const should always be initialized while defining. In the program below,  assignment to pi gives a compiler error because pi is defined as const and code is trying to modify this. int main () { const float pi = 22.0/7 ; int radius = 12 ; radius ++ ; /*ok*/ pi = 3.14 ; /*error*/ }   Constant parameters to functions Even function parameters can be const ant. We have earlier discussed that making a reference parameter as constant will avoid the function from accidentally modifying the argument. void printnum ( int & n) { cout << n ++ ; } void printnum2 ( const int & n) { cout <&l