CS202 Computer Science II
FAQ Newsboard  Telnet Email TA Lectures Assignments Man pages Help

Friends and Operator Overloading

Layered Software

Consider the following class specification:
class NewType {
public: 
  void Func(int n);
  .
  .
  .
  NewType(int JarUnits); // Constructor

private:
  float privateFloat;
  JarType privateJar;
};
Note that NewType is based upon JarType. In other words an instance of JarType is a member of NewType. Member functions of NewType must use dot (.) notation and appropriate JarType operations when accessing privateJar. For example:
void NewType::Func(int n)
{
   privateJar.Add(n);
   if(privateJar.Quantity() > 50)
      privateFloat = 3.4 * float(n);
}


Now how do we initialize privateJar when NewType's constructor is invoked.

NewType someObject(20);
Where we want to initialize privateJar to have 20 units (the argument to someObject). So how do we pass this parameter (20) to privateJar's constructor as well?

C++ has Constructor Initializer Lists just for this purpose. A constructor initializer list is used whenever a class constructor needs to pass a parameter to a member's constructor.

Here is NewType's constructor with its constructor initializer list.

NewType::NewType(int jarUnits) : privateJar(jarUnits)
{
   privateFloat = 0.0;
}

When a class instance is created, its constructor body is executed after all constructors for its members have been executed. Thus when a NewType instance is created, the constructor privateJar is first invoked. Only after privateJar has been created and initialized does the body of NewType's constructor execute.

Here are a couple of rules that C++ enforces for constructor initializer lists.

  1. A constructor initializer list is places in the function definition but not in the function prototype.
  2. If a class has two or more class instances as members
    class MyClass{
    public:
      .
      .
      .
      MyClass(int n); // Constructor
    
    private:
      int privateVal;
      YourClass alpha;
      TheirClass beta;
    };
    
    assuming that YourClass and TheirClass have parametrized constructors, then in the definition of MyClass's constructor, the constructor initializer list requires a comma to separate the class to member constructors.
    MyClass::MyClass(int n) : alpha(n), beta(n+5)
    {
       privateVal = n;
    }
    
    The order of constructors in the constructor initializer list does not matter, it is the order of declaration of alpha and beta in the declaration of MyClass that decides the order in which constructors in the constructor initializer list are executed.


Friends

So far we have looked at members and functions that use only one class instance as an operand. Add for example operates on a single JarType instance.

JarType jar1, jar2;

jar1.Add(34);
Add operates on jar1. However there are occasions when a member or operator needs to opeate on two or more class instances. For example, we may want to design a function that returns true if two JarType instances are equal.

We may for example want to be able to write

if (Equal(jar1, jar2))
   DoSomething();
The C++ == is not defined for programmer defined classes so we have to write our own equivalent. We will use two approaches to write such an "Equal" function.
  1. Equal is a member function of the JarType class.
  2. Equal is a friend of the JarType class.
Here's jar.h, the JarType specification file.

class JarType {
public:
   .
   .
   .
   Boolean Equal(JarType otherJar) const;
     //POST: FCTVAL == (This jar and otherJar contain
     // the same number of of units
   .
   .
   .
private:
   int numUnits;
};
Clients must use the dot (.) notation to refer to Equal , for example
   if (jar1.Equal(jar2))
    .
    .
    .
and Equal would look like
Boolean JarType::Equal(JarType otherJar) const
{
   return (numUnits == otherJar.numUnits);
}

However most programmers would prefer a more traditional way of writing code

   if (Equal(jar1, jar2))
     .
     .
     .
In this case Equal could not be a member of JarType because the function is not accessed using dot notation. On the other hand, if Equal is not a member, it cannot access numUnits since numUnits is a private member of JarType.

There is a way out of this catch-22. A class may grant permission to a nonmember function to access its private members by declaring that function to a friend of the class. A friend specification looks like

class SomeClass {
public:
   .
   .
   .
   friend SomeType SomeFunc(....);

private:
   .
};

The scope of a friend function is that of the code surrounding the class 
declaration. A client doesn't need to use dot notation to invoke friends.

Here's the specification file for JarType declaring Equal a friend.

class JarType {
public:
   .
   .
   .
   friend Boolean Equal(JarType firstJar, JarType secondJar);
   .
   .
private:
   int numUnits;
};
Here's Equal's definition.
Boolean Equal(JarType firstJar, JarType secondJar)
{
   return (firstJar.numUnits == secondJar.numUnits);
}
Note that Equal, a friend, is able to access private members of the class that declared it as a friend.

A client could then use Equal as follows

   jar1.Add(25);
   cin >> someInt;
   jar2.Add(somInt);
   if(Equal(jar1, jar2))
      DoSomething();

Friends provide a more traditional syntax for functions/operators that use two or more instances of a class.


Operator Overloading

An overloaded operator can have more than one meaning depending on the data types of its operands. Consider the specification of a member Sum of SomeClass which returns a class instance representing the sum of two other class instances.

SomeClass Sum(SomeClass ob1, SomeClass ob2);
We can overload the + operator to do the same thing. The only thing that needs changing is the name of the member Sum. The notation for overloading an operator is

operator

Thus to overload + to have the same meaning as the member Sum, we write:

SomeClass operator<+>(SomeClass ob1, SomeClass ob2);
Clients can invoke this operator in two ways:
  1. obj3 = operator+(obj1, obj2) or

  2. obj3 = obj1 + obj2
The second notation is just what we were after. If we overload the == operator to replace Equal job as a member to provide an even "better" syntax.
class JarType {
public:
   .
   .
   .
   Boolean operator<==> (JarType otherJar);
     //POST: FCTVAL == (This jar and otherJar contain
     // the same number of of units
   .
private:
   .
};
We only need to replace the name Equal with the operator<==> in the definition of the member function Equal.
Boolean JarType::operator<==>(JarType otherJar) const
{
   return (numUnits == otherJar.numUnits);
}

Guidelines for operators overloading

  1. All C++ operators may be overloaded except the following four operators
    1. ::
    2. .
    3. sizeof
    4. ?:
  2. Overloading is allowed only if at least one operand is a class instance. For example: You cannot overload an operator to take two ints as operands.
  3. It is impossible to
    1. change the standard precedence of operators.
    2. define new operator symbols not already in the C++ language.
    3. change the number of operands of an operator
  4. Overloading unary operators.
    Let a be of type SomeClass, and we want to overload the unary minus operator(-), Then the expression
       -a
    
    is equivalent to the function invocation
    1. a.operator-() if operator- is a member function.
    2. operator-(a) if operator- is a friend.
  5. Overloading binary operators.
    Let a be of type SomeClass, and suppose b is of type AnyType and we want to overload the binary addition operator (+), Then the expression
       a + b
    
    is equivalent to the function invocation
    1. a.operator+(b) if operator+ is a member function.
    2. operator+(a, b) if operator+ is a friend.
  6. Overloading ++ requires client code to sue pre-increment form ++someObject. Same for --
  7. Special rules apply to overloading the =, (), []. One restriction is that the operator functions must be class members, not friends
  8. Many meanings for an operator can co-exist, as long as the compiler can distinguish among the data types of the operands.