This Is A Discussion of Overloading =

Consider the following class that implements complex numbers. Remember that a complex number consists of a real part and an imaginary part - here denoted x,y.

class Complex
{
   public:
       double x,y;

   Complex(double xSet, double ySet): x(xSet), y(ySet) {}
   Complex& operator=(const Complex& cAssign)
     {x = cAssign.x; y = cAssign.y; return *this;}
  };

void main()
  {
     Complex A(0,0), B(1,1);
     A = B; // uses overloaded assignment operator.
  }

Lets look at some salient features of this class and its overloading of the = operator.

An application defined assignment function (operator=) must be declared as a non-static method accepting one parameter.

Remember that every non-static member function has a this pointer. The this pointer is a pointer to the calling object. In this case that would be the object A.

The Complex at the beginning of the declaration indicates return type Complex. The addition of the ampersand (Complex&) indicates that the return will be by reference. The reference returned by a compiler generated assignment operator is to the object being assigned to. In this case A.

Recall that a declaration such as
int &ref_i = int_varb;
makes ref_i a reference to int_varb. Note that int_varb is NOT a pointer but a variable.

That is why we must return a dereference to the this pointer as in: return *this Remember that placing * in front of a pointer causes us to get the value the pointer is pointing to. We need to use this syntax to correctly complete the return by reference declared as Complex&

As to why we must return the *this, when the data has already been placed in A... In other words, why not declare the return type void and just quit at the end. The answer is: We are doing an assignment, therefore we must return something to A. If we had some way of declaring:

   (void)operator=(const Complex& cAssign)

we could dispense with returning *this. The body would become:

     {x = cAssign.x; y = cAssign.y;}

Note that the reason we return something rather than nothing is to support multiple assignments such as:

A = B = C;

I hope this answers all the questions you had in class - Ken


Addendum: There is one more important issue that was not covered in the above discussion. Suppose that during assignment memory has to be allocated prior to copying data from the right hand side of the equation. In this case, an extra step must be taken. We must make sure that the user has not done a self-referential assignment like A=A as allocating for *this would wipe out the data in A. To prevent the problem, put the entire assignment inside an if check like:

if (&cAssign != this ){
  delete Z;
  Z = new Holder();
  Z->x = cAssign.Z->x; 
  Z->y = cAssign.Z->y;
  } 
return *this;  

Here I am just assuming that the class now puts the data into a structure Holder that has 2 elements, x and y. Notice that the if(&cAssign != this) prevents allocation to the Z pointer, which, if this was a call by a self-referential assignment, would wipe out Z.

You can prove to yourself that this works, by cutting and pasting the following code into the cygwin compiler and executing it.

#include 
#include 

struct Holder{
  double x,y;
};

class Complex
{
   public:
      Holder *Z; // added just so that new is required in constructor

   // new constructor that allocates Z
   Complex(double xSet, double ySet) {
	   Z = new Holder(); 
	   Z->x = xSet; 
	   Z->y = ySet;
   }

   // copy constructor
   Complex(Complex const &src) {
	   Z = new Holder(); 
	   Z->x = src.Z->x; 
	   Z->y = src.Z->y;
   }

   // destructor now required.
   ~Complex(){delete Z;}; 

   // safe overload of the assignment operator
   Complex& operator=(const Complex& cAssign)
     {
     if (&cAssign != this){
       delete Z;
       Z = new Holder();
       Z->x = cAssign.Z->x;
       Z->y = cAssign.Z->y;
     }
     return *this;
     }

  };

main()
  {
     Complex A(13,13), B(666,666);
	 Complex C(B);
	 // Complex C(C); // not allowed so self reference case is not an issue
     cout << A.Z->x << " " << A.Z->y << " "; 
	 cout << B.Z->x << " " << B.Z->y << " "; 
	 cout << C.Z->x << " " << C.Z->y << " " << endl;
     A = B; // uses overloaded assignment operator
     cout << A.Z->x << " " << A.Z->y << " " << B.Z->x << " " << B.Z->y << " " << endl;
     A = A; // who would expect this?
     cout << A.Z->x << " " << A.Z->y <<  endl;
     return 0;
}


The output should be:

13 13 666 666
666 666 666 666
666 666

Try the program without the check for self reference, by doing this:


   // unsafe overload of the assignment operator
   Complex& operator=(const Complex& cAssign)
     {
    // if (&cAssign != this){
       delete Z;
       Z = new Holder();
       Z->x = cAssign.Z->x;
       Z->y = cAssign.Z->y;
    // }
     return *this;
     }

Your output should be:

13 13 666 666 666 666
666 666 666 666
2.43297e+159 666 (UNDETERMINED)

Note: the above was what I got on my computer. There is no guarantee as to what these values will be. They could be 666 666 which would be really bad as you might not know you have a problem.

File showing assignment operator shallow copy error in action.

Shallow Copy

For a discussion of the copy constructor:

Copy Constructor