>

Monday, 14 December 2015

Operator Overloading

Operator Overloading:

Operator overloading is the ability to tell the compiler how to perform a certain operation when its corresponding operator is used on one or more variables.
By overloading standard operators on a class, you can exploit the intuition of the users of that class. This lets users program in the language of the problem domain rather than in the language of the machine. The ultimate goal is to reduce both the learning curve and the defect rate.
C++ supports a set of operators for its built-in types. To use these operators for user defined types, these operators should be defined in the context of the class. For example, there is no built-in operator to add two complex numbers. To add two complex numbers, the + operator should be overloaded in a class as shown below.

+ Operator:
Below program overloads + operator to add two complex numbers
#include "stdafx.h"
#include <iostream>
using namespace std;

class Complx
{
     double real,imag;
public:
     Complx(int real,int imag):real(real),imag(imag)
     {

     }
     Complx operator+(const Complx& C)
     {
           Complx temp(0,0);
           temp.real = this->real + C.real;
           temp.imag = this->imag + C.imag;
           return temp;
     }
    
};

int _tmain(int argc, _TCHAR* argv[])
{
     Complx x(4,5);
     Complx y(4,5);
Complx Z = x + y; // x.operator+(y) y is a parameter and it should      return complx object
     return 0;
}
Here the complx class has two data members real and imag.In operator+() method,we have added real and imaginary parts of the class instances seperately to add two complex numbers.


Below fig shows the way to build the operator+ signature.
In main() function,the call to add two complex numbers is expanded as shown below.

An overloaded operator is called an operator function. You declare an operator function with the keyword operator preceding the operator.


Note 1: The following are the list of the operators which can be overloaded.

+
-
*
/
%
^
&
|
~
!
=
< 
> 
+=
-=
*=
/=
%=
^=
&=
|=
<< 
>> 
<<=
>>=
==
!=
<=
>=
&&
||
++
--
,
->*
->
( )
[ ]
new
delete
new[]
delete[]



where () is the function call operator and [] is the subscript operator.
You can overload both the unary and binary forms of the following operators:
+
-
*
&




Note 2: The following operators cannot be overloaded

.  (member selection)
.* (member selection through pointer to function)
:: (scope resolution)
?:
The above parameters take a name rather than a value as their second operand and provide the primary means of referring to members. Allowing them to overload will lead to subtleties.
You cannot overload the preprocessor symbols # and ##.
Note3:
An overloaded operator (except for the function call operator) cannot have default arguments or an ellipsis in the argument list.
void operator-(int i = 0) //error C2831: 'operator -' cannot have 
                           default parameters
{                         //error C2333: 'Complx::operator -' : error
                          in function declaration; skipping function
                          body
}

Overloading Binary Operators:
Binary unary operator can be overloaded with either a nonstatic member function that has one parameter, or a nonmember function that has two parameters.
A nonstatic member function that overloads this operator would have the following form:
 return_type operator@(T)
A nonmember function that overloads the same operator would have the following form:
return_type operator@(T, U)
*Operator:
#include "stdafx.h"
#include <iostream>
using namespace std;

class Complx
{
     double real,imag;
public:
     Complx(int real,int imag):real(real),imag(imag)
     {

     }
Complx operator*(const Complx &C)
     {
           Complx temp(0,0);
           temp.real = (this->real * C.real);
           temp.imag = (this->imag * C.imag);
           return temp;
     }

};
int _tmain(int argc, _TCHAR* argv[])
{
     Complx x(4,5);
     Complx y(4,5);
     Complx Zmultiply = x * y;
     return 0;
}



Overloading Function call operator:
#include "stdafx.h"
#include <iostream>
using namespace std;

class FuncCall
{

public:
     FuncCall(){}
     void operator()(int a,int b,char z,char c){}
     void operator()(int a,int b){}
};

int _tmain(int argc, _TCHAR* argv[])
{
     FuncCall f1,f2;
     f1(4,5,'Z','a'); // error C2064: term does not evaluate to a function taking 4 arguments
                      //f1.operator()(..params..)
                      //To support this overload function call operator ()

     f1(4,5);
     return 0;
}
The function call operator, when overloaded, does not modify how functions are called. Rather, it modifies how the operator is to be interpreted when applied to objects of a given type.
You overload the function call operator, operator (), with a nonstatic member function that has any number of parameters. If you overload a function call operator for a class its declaration will have the following form:
return_type operator()(parameter_list)
Unlike all other overloaded operators, you can provide default arguments and ellipses in the argument list for the function call operator.
class Point
{
private:
  int x, y;
public:
  Point() : x(0), y(0) { }
  Point& operator()(int dx, int dy) {
    x += dx;
    y += dy;
    return *this;
  }
};

int main() {
  Point pt;

  // Offset this coordinate x with 3 points
  // and coordinate y with 2 points.
  pt(3, 2);
}





Overloading Assignment operator:
#include "stdafx.h"
#include <iostream>
using namespace std;

class Assign
{
     int a;
public:
     Assign()
     {
           cout<<"constructor"<<endl;
     }
     Assign &operator=(Assign &a)
     {
           return a;
     }
     Assign &operator=(int a)
     {
           this->a = a;
           return *this;
     }
};
int _tmain(int argc, _TCHAR* argv[])
{
     Assign a1,a2,a3;

     //TO support below expression,we need to overload assignment operator
     a1 = 5; // a1.operator=(5)

     a2 = a1; //a2.operator=(a1)

     return 0;
}
The assignment x1 = x2 calls the copy assignment operator X& X::operator=(X&). The assignment x1 = 5 calls the copy assignment operator X& X::operator=(int). The compiler implicitly declares a copy assignment operator for a class if you do not define one yourself. Consequently, the copy assignment operator (operator=) of a derived class hides the copy assignment operator of its base class..If we have some pointer data members then we should overload = operator to avoid shallow copy.
Note: assignment operator can be overloaded with a nonstatic member function that has only one parameter. You cannot declare an overloaded assignment operator that is a nonmember function.
Overloading subscription operator:
You overload operator[] with a nonstatic member function that has only one parameter. The following example is a simple array class that has an overloaded subscripting operator.

#include "stdafx.h"
#include <string>
#include <iostream>
using namespace std;

template <class T>
class MyArray
{
     T *arr;
     T size;

public:
     MyArray(T size)
     {
           arr = new T[size];
           this->size = size;
     }
     ~MyArray()
     {
           delete[] arr;
           arr = NULL;
     }
     T &operator[](T location)
     {
           if(location<0)
                cout<<"index out of range"<<endl;
           else
                return arr[location];
     }
};

int _tmain(int argc, _TCHAR* argv[])
{
   MyArray<int> Col(3);
   Col[0] = 45;
   Col[2] = 55;
   cout<< Col[0] <<endl;
   return 0;
}
The expression x[1] is interpreted as x.operator[](1) and calls int& MyArray<int>::operator[](const int).

Overloading Pre and post fix operators:
The prefix increment operator ++ can be overloaded with either a nonmember function operator that has one argument of class type or a reference to class type, or with a member function operator that has no arguments.


#include "stdafx.h"

class A
{
     int a;
public:
     A(){}

     //pre-increment operator
     int operator++()
     {
           a = a + 1;
           return this->a;
     }

     void operator--()
     {
           a = a - 1;
     }

     //post increment operator - add a parameter to
     // method to make it post increment operator
     void operator++(int)
     {
            a= a+1;
     }
     void operator--(int a)
     {
     }
};
int _tmain(int argc, _TCHAR* argv[])
{
     A a;
     int value = ++a;
     --a;
      a++;
     return 0;
}
The postfix increment operator ++ can be overloaded for a class type by declaring a nonmember function operator operator++() with two arguments, the first having class type and the second having type int. Alternatively, you can declare a member function operator operator++() with one argument having type int. The compiler uses the int argument to distinguish between the prefix and postfix increment operators.


#include <iostream>
using namespace std;
class Digit
{
 
private:
    int m_digit;
public:
    Digit(int ndigit=0){
     m_digit=ndigit;
                        }
    Digit& operator++();//prefix
    Digit& operator--();   //prefix
        Digit operator++(int);
        Digit operator--(int);
        int get() const { return m_digit;}
};
Digit& Digit::operator++(){
 
   ++m_digit;
   return *this;
}
Digit& Digit::operator--(){
 --m_digit;
 return *this;
 
}
Digit Digit::operator++(int){
Digit cresult(m_digit);
++(*this);
return cresult;
 
 
}
    Digit Digit::operator--(int){
Digit cresult(m_digit);
--(*this);
return cresult;
 
 
}
    int main(){
 
     Digit cDigit(5);
      ++cDigit;
        cDigit++;
         cout<<cDigit.get()<<endl;
         cout<<cDigit.get()<<endl;
 
     return 0;
    }
The pre- and post-increment are two distinct operators, and require separate overloads.
C++ doesn't allow overloading solely on return type, so having different return types as in your example wouldn't be sufficient to disambiguate the two methods.
The dummy argument is the mechanism that the designer of C++ chose for the disambiguation.





Guidelines:
  1. Use common sense. If your overloaded operator makes life easier and safer for your users, do it; otherwise don't. This is the most important guideline. In fact it is, in a very real sense, the only guideline; the rest are just special cases.
  2. If you define arithmetic operators, maintain the usual arithmetic identities. For example, if your class defines x + y and x - y, then x + y - y ought to return an object that is behaviorally equivalent to x. The term behaviorally equivalent is defined in the bullet on x == y below, but simply put, it means the two objects should ideally act like they have the same state. This should be true even if you decide not to define an == operator for objects of your class.
  3. You should provide arithmetic operators only when they make logical sense to users. Subtracting two dates makes sense, logically returning the duration between those dates, so you might want to allow date1 - date2 for objects of your Date class (provided you have a reasonable class/type to represent the duration between two Date objects). However adding two dates makes no sense: what does it mean to add July 4, 1776 to June 5, 1959? Similarly it makes no sense to multiply or divide dates, so you should not define any of those operators.
  4. You should provide mixed-mode arithmetic operators only when they make logical sense to users. For example, it makes sense to add a duration (say 35 days) to a date (say July 4, 1776), so you might define date + duration to return a Date. Similarly date - duration could also return a Date. But duration - date does not make sense at the conceptual level (what does it mean to subtract July 4, 1776 from 35 days?) so you should not define that operator.
  5. If you provide constructive operators, they should return their result by value. For example, x + y should return its result by value. If it returns by reference, you will probably run into lots of problems figuring out who owns the referent and when the referent will get destructed. Doesn't matter if returning by reference is more efficient; it is probably wrong. See the next bullet for more on this point.
  6. If you provide constructive operators, they should not change their operands. For example, x + y should not change x. For some crazy reason, programmers often define x + y to be logically the same as x += y because the latter is faster. But remember, your users expect x + y to make a copy. In fact they selected the + operator (over, say, the += operator) precisely because they wanted a copy. If they wanted to modify x, they would have used whatever is equivalent to x += y instead. Don't make semantic decisions for your users; it's their decision, not yours, whether they want the semantics of x + y vs. x += y. Tell them that one is faster if you want, but then step back and let them make the final decision — they know what they're trying to achieve and you do not.
  7. If you provide constructive operators, they should allow promotion of the left-hand operand (at least in the case where the class has a single-parameter ctor that is not marked with the explicit keyword). For example, if your class Fraction supports promotion from int to Fraction (via the non-explicit ctor Fraction::Fraction(int)), and if you allow x - y for two Fraction objects, you should also allow 42 - y. In practice that simply means that your operator-() should not be a member function of Fraction. Typically you will make it a friend, if for no other reason than to force it into the public: part of the class, but even if it is not a friend, it should not be a member.
  8. In general, your operator should change its operand(s) if and only if the operands get changed when you apply the same operator to intrinsic types. x == y and x << y should not change either operand; x *= y and x <<= y should (but only the left-hand operand).
  9. If you define x++ and ++x, maintain the usual identities. For example, x++ and ++x should have the same observable effect on x, and should differ only in what they return. ++x should return x by reference; x++ should either return a copy (by value) of the original state of x or should have a void return-type. You're usually better off returning a copy of the original state of x by value, especially if your class will be used in generic algorithms. The easy way to do that is to implement x++ using three lines: make a local copy of *this, call ++x (i.e., this->operator++()), then return the local copy. Similar comments for x-- and --x.
  10. If you define ++x and x += 1, maintain the usual identities. For example, these expressions should have the same observable behavior, including the same result. Among other things, that means your += operator should return x by reference. Similar comments for --x and x -= 1.
  11. If you define *p and p[0] for pointer-like objects, maintain the usual identities. For example, these two expressions should have the same result and neither should change p.
  12. If you define p[i] and *(p+i) for pointer-like objects, maintain the usual identities. For example, these two expressions should have the same result and neither should change p. Similar comments for p[-i] and *(p-i).
  13. Subscript operators generally come in pairs; see on const-overloading.
  14. If you define x == y, then x == y should be true if and only if the two objects are behaviourally equivalent. In this bullet, the term "behaviorally equivalent" means the observable behavior of any operation or sequence of operations applied to x will be the same as when applied to y. The term "operation" means methods, friends, operators, or just about anything else you can do with these objects (except, of course, the address-of operator). You won't always be able to achieve that goal, but you ought to get close, and you ought to document any variances (other than the address-of operator).
  15. If you define x == y and x = y, maintain the usual identities. For example, after an assignment, the two objects should be equal. Even if you don't define x == y, the two objects should be behaviorally equivalent (see above for the meaning of that phrase) after an assignment.
  16. If you define x == y and x != y, you should maintain the usual identities. For example, these expressions should return something convertible to bool, neither should change its operands, and x == y should have the same result as !(x != y), and vice versa.
  17. If you define inequality operators like x <= y and x < y, you should maintain the usual identities. For example, if x < y and y < z are both true, then x < z should also be true, etc. Similar comments for x >= y and x > y.
  18. If you define inequality operators like x < y and x >= y, you should maintain the usual identities. For example, x < y should have the result as !(x >= y). You can't always do that, but you should get close and you should document any variances. Similar comments for x > y and !(x <= y), etc.
  19. Avoid overloading short-circuiting operators: x || y or x && y. The overloaded versions of these do not short-circuit — they evaluate both operands even if the left-hand operand "determines" the outcome, so that confuses users.
  20. Avoid overloading the comma operator: x, y. The overloaded comma operator does not have the same ordering properties that it has when it is not overloaded, and that confuses users.
  21. Don't overload an operator that is non-intuitive to your users. This is called the Doctrine of Least Surprise. For example, altough C++ uses std::cout << x for printing, and although printing is techincally called inserting, and although inserting sort of sounds like what happens when you push an element onto a stack, don't overload myStack << x to push an element onto a stack. It might make sense when you're really tired or otherwise mentally impaired, and a few of your friends might think it's "kewl," but just say No.
  22. Use common sense. If you don't see "your" operator listed here, you can figure it out. Just remember the ultimate goals of operator overloading: to make life easier for your users, in particular to make their code cheaper to write and more obvious.
Problem:
Find no of objects created on the heap and no of objects created on the stack
#include "stdafx.h"
#include <new>
#include <cstdlib>
#include <iostream>
using namespace std;

class A
{
private:
     static int nStack; //count for objects created on stack
     static int nHeap;  //count for objects created on heap
public:
     A()
     {
           nStack++;
     }
     static int getStackObjNo() //returns stack objs count
     {
           return nStack;
     }

void* operator new(size_t size) //overload new such that it calls 
                                  malloc for memory                        
     {                               //allocation and calls constructor
                                        for initialization
           void* p = malloc(size);
           nHeap++;
           return p;
     }

     static int getHeapObjNo(){return nHeap;} //returns heap objects count

     void operator delete(void *p){free(p);} //overload delete operator


};
int A::nStack = 0;
int A::nHeap = 0;


int _tmain(int argc, _TCHAR* argv[])
{
     A *a = new A();
     A a1;
     A *b = new A();
     cout<<A::getStackObjNo()<<endl;
     cout<<A::getHeapObjNo()<<endl;
     delete a;
     delete b;
     return 0;
}


Which is more efficient: i++ or ++i?
++i is sometimes faster than, and is never slower than, i++.
For intrinsic types like int, it doesn't matter: ++i and i++ are the same speed. For class types like iterators or the previous FAQ's Number class, ++i very well might be faster than i++ since the latter might make a copy of this object.


0 comments:

Post a comment