2-2) Operator Overloading


Most C++ Operators can be overloaded (except ::, sizeof, ., .*, :?)

One argument must be a user-defined type.
     You can define Person + Person, Person + int, but not int + int.



C++ Operators (in  precedence groups)
:: Scope Resolution BankAccount::print();

. Object member selection
-> Pointer member selection
 [] subscripting
 () function call
 type() construction
  typeid type identification
  ++ Post Increment
  -- Post Decrement
  dynamic_cast,static_cast,reinterpret_cast, const_cast
account.getBalance();
p->getBalance();
v[i] = 5;
a(i);
Person("Lou");
typeid(x)
index++;
index--;
const_cast<Person &> r;


sizeof
++ Pre increment
-- Pre decrement
~ Bitwise NOT
! Logical NOT
- Negation
+ Unary Plus sign
& Address
* dereference
new, delete
(type) Typecast
sizeof(anObject); sizeof(aType);
++index;
--index;
~expression
!expression
- expression
+ expression
&anObject;
*ptr;
new Person; new Person[100];
(Person *) p;


.*    pointer to member
->* pointer to pointer member
object.*method_ptr;
ptr->*method_ptr;


* Multiply
/ Divide
% Modulus
x*y;
x/y;
x%y;


+ Addition
- Subtraction
x+y;
x-y;


<< Left shift
>> Right shift
expression << expression
expression >> expression


< Less Than
<= Less or Equal
> Greater Than
>= Greater or Equal
x < y;
x <= y;
x > y;
x >= y;


== Equal
!= Not Equal
x == y;
x != y;


& bitwise AND expr & expr

^ bitwise exclusive OR expr ^ expr

| bitwise OR expr | expr

&& logical AND expr && expr

|| logical inclusive OR expr || expr

= Assignment
+= op-assign (*=, /=, %=, +=, -=, 
<<= shift left assign and also >>= shift right assign
&= AND assign, also |=, and ^=
x = y;
x += y; //x = x + y
x <<= y;
x &= y;


?: condidtional expression flag ? expr1 : expr2
if (flag) expr1 else expr2;


Throw    throw exception throw expr

, sequencing operator expr, expr

operators in red cannot be overloaded

*ptr[i]; means *(ptr[i]);
*ptr->foo(); means *(ptr->foo());



C++ Operators can be overloaded as member functions or global procedures

Person me, you;
me + you;     // means me.operator+(you);

me + him + her  //means (me.operator+(him)).operator(her);

Option 1: //as member function
class Person{
    Person & operator+(const Person & p) {...; return *this;}
};

Option 2: //as global procedure
   Person & operator+(Person & p1, const Person & p2) {...; return p1}



Output Stream Insertion Operator

Person me, you;
cout << me << you;

Option 1: //WRONG
class Person{
    ostream & operator<<(const Person & p) {...}
};
This cannot work because cout << x means cout.operator<<(x) and not x.operator<<(cout);

Option 2: //global procedure friend procedure

class Person{
    friend ostream & operator<<(ostream &, const Person &);
    ...
    private:
      char * name;
      int age;
};

ostream & operator<<(ostream & o, const Person & person) {
    o << person.name << person.age;
    return o;
}

Option 3: //global procedure that invokes a public printOn  method (Best Approach)

class Person{
    public:
    void printOn(ostream & output) {
       output << name << age;
    }
    ...
    private:
      char * name;
      int age;
};

ostream & operator<<(ostream & o, const Person & person) {
    person.printOn(o);
    return o;
}



Operator->

class Person {
   public: char * getName() {...}
};

class PersonPointer {
   Person * operator->(){...}
};

Person me("Lou");
PersonPointer p(me);

p->getName();  //means (p.operator->())->getName();

Notice the -> is in effect applied twice



Operator=

x = y = z;     //means x.operator(y.operator(z));

Customer& Customer::operator=(const Customer& custRef){
 if (this != &custRef) { //do nothing for x = x
  delete [] name; //delete old memory
  delete [] address;
  name = new char[strlen(custRef.name) + 1]; //allocate new memory
  strcpy(name, custRef.name);
  address = new char[strlen(custRef.address) + 1];
  strcpy(address, custRef.address);

 }
 return *this;  //to allow multiple assignments
}

compare with copy constructor
 

Customer::Customer(const Customer& custRef){
  name = new char[strlen(custRef.name) + 1];
  strcpy(name, custRef.name);
  address = new char[strlen(custRef.address) + 1];
  strcpy(address, custRef.address);
 }



Const and non-const versions of operators
Operator[]

class Vector{
    T & operator[](int i) {return buffer[i];}
    T operator[](int i) const {return buffer[i];}
    ...
    private:
      T * buffer;
};

Vector v;
const Vector vc;

v[3] = vc[4];

will call non const version of operator for lhs and const version for rhs.

vc[5] = v[2]; //NOT ALLOWED

void foo(Vector & v1, const Vector & v2) {
        v1[1] = x;
        v1[2] = v2[3];
}



Unary Increment and Decrement

++x;    //means x.operator++();
x++;    //means x.operator++(int);

--x;    //means x.operator--();
x--;    //means x.operator--(int);
 

The (int) parameter is not used at all it is just there to distinquish the two operators that have the same name, or symbol.

T & operator++() {item++; //do whatever ++ means for the class
                  return *this; }

T operator++(int) {T temp = *this;
                   item++;
                   return temp; }



AutoPointer Example

A problem in C++ programming is deleting a pointer without deleting what it points at and leaving memory on the heap. One possible solution is to have a pointer that, in effect, owns the object it points to. If the pointer is deleted, the object it points to is also deleted. To create such a pointer we can make an object that behaves like a pointer. Such "owning" pointers are often called Auto-pointers.

sample code


class BankAccount{
public:
BankAccount(int acctNum, float amount = 0) :
     accountNumber(acctNum), balance(amount){}
     ~BankAccount(void) {cout << "Destroying Acct:" << accountNumber << "\n";}
     void deposit(float amount){balance += amount;}
     void withdraw(float amount){balance -= amount;}
     void printOn(ostream & o) const {
         o << "Acct:" << accountNumber << " $" << balance << "\n";
     }
private:
    const int accountNumber;
    float balance;
};

ostream & operator<<(ostream & out, const BankAccount & b){
    b.printOn(out);
    return out;
}


template <class T>
class Autopointer{
   T * p;
   Autopointer<T> & operator=(const Autopointer<T> & p) {return *this;}
   Autopointer(const Autopointer<T> & p) {}
public:
    Autopointer(): p(NULL){}
    Autopointer(T * ptr):p(ptr) {}
    ~Autopointer(void){if(p != NULL) delete p; }
    Autopointer<T> & operator= (T * ptr)
      {if (p != NULL) delete p;
       p = ptr;
       return *this;
      }
    T & operator*() {return *p;}
    T * operator->(){return p;}
};



int main(void) {
   Autopointer<BankAccount> bp1(new BankAccount(1000)), bp2, bp3;
   BankAccount * bp4;
   bp2 = new BankAccount(1001);
   bp4 = new BankAccount(1002);
   //bp4 = bp2 not allowed
   bp1->deposit(100.0);
   bp2->deposit(50.0);
   bp4->deposit(1000.0);
   cout << *bp1 << "\n" << *bp2 << "\n"  << *bp4 << "\n";
   return 0;
   //notice bp3 is never used
}

/*OUTPUT
Acct:1000 $100

Acct:1001 $50

Acct:1002 $1000

Destroying Acct:1001
Destroying Acct:1000

(Notice account #1002 was not deleted -memory leak)
*/