Polymorphism in C++

Polymorphism is a useful feature that depends entirely upon inheritance. Make sure you understand inheritance first.

        Continuing with the example of the representation of the people that a company might be interested in (in the section on inheritance), we ended up with five classes defined like this:
class person
{ protected:
    char *name, *ident, *address, *phone;
   public:
     person(char *n, char *i, char *a, char *p);
     char *get_name(void);
     char *get_ident(void);
     char *get_address(void);
     char *get_phone(void);
     void set_address(char *a);
     void set_phone(char *p);
     void print(void); };

 class customer: public person
 { protected:
     int rating, balance;
   etc etc etc };

 class employee: public person
 { protected:
     char *jobtitle, *supervisor;
     int salary;
   etc etc etc };

 class family: public person
 { protected:
     char *employee;
   etc etc etc };

 class stockholder: public person
 { protected:
     int numshares;
   etc etc etc };
And we remember what this means. Through the mechanism of inheritance, all of the four derived classes have not only the members and methods that we write in their own declarations, but they also inherit the members name, ident, address, and phone, and the methods get_name, get_ident, get_address, get_phone, set_address, set_phone, and print from person.
        This means that no matter what extra functionality these classes have, they are always guaranteed to have at the very least, all the functionality of a person. In other words, anything you can possibly do to or with a person, you must also be able to do to or with a customer. A customer is a kind of person.
        That means that it would be safe to relax the type checking rules of C a little. The rules about matching types are generally in place to ensure that all operations you might program can actually be performed. For instance, it makes sense to find the square root of a float, but it doesn't make sense to find the square root of a pointer to a string. On the other hand, it makes sense to find the first character of the string that a string pointer points to, but it doesn't make sense to find the first character of the string that a float points to. So we are required to keep floats and string-pointers in different kinds of variables, and bsaed on how we declare those variables, the compiler can check that we are doing sensible operations. We will never be allowed to store a string-pointer in a float variable, because we might then try to find its square root.
        However, this reasoning doesn't apply to people and customers, because we have just observed that a customer is guaranteed to be able to do anything that a person can do, so there can be no danger in storing a customer-type object in a person-type variable. (The converse is not true: customers can do things that people can't). This is the general rule of polymorphism: When one class (A) is derived from (or inherits) another class (B), it is guaranteed that an A object can do anything that a B object can do, therefore it is permitted to store A objects in B variables.
{ person P1("jim","K192A","123 Ant St","532-5841");
  person P2("Meg","W738R","456 Bat St","737-1092");
  customer C1("Bat","C71828-54","222 Cat St","123-8765",60,0);
  // it is OK to do this assignment
  P2=C1;
  // because all operations allowed on P2 are meaningful for a customer
  P2.set_address("987 Dog St");
  P2.print();
  x=P2.get_name();
  // all of those are OK because even though P2 contains a customer, those
  // methods must exist for all customers

  // On the other hand, it is NOT OK to do this assignment
  C1=P1;
  // because some things we might do with a customer will be meaningless
  C1.set_rating(35);
  x=C1.get_balance();
  // those two statements are allowed because C1 was declared as a customer
  // and customers have set_rating and get_balance methods. However, the
  // variable C1 actually contains a person object, and persons do not have
  // set_rating or get_balance methods, or even rating and balance members.

All that, as you'll see soon, could be very useful. Naturally, there is a fly in the ointment. A customer is bigger than a person; it has more members, so it takes up more space. A person has 4 string-pointer members so a person variable would probably occupy 16 bytes of storage (methods don't usually occupy any memory); a customer has 5 string-pointers and 2 integers, so probably needs 28 bytes of space. So polymorphism as illustrated above just won't work. It is safe to store a customer object in a person variable, but impossible because it just won't fit.
        Fortunately, there is a simple fix: Pointers are always the same size, regardless of what they point to. That means that it is both safe and possible to store a customer pointer in a person pointer variable. Yet another reason why we should just about always be using pointers to objects instead of direct objects in programs. The corrected example is:
{ person *P1=new person("jim","K192A","123 Ant St","532-5841");
  person *P2;
  customer *C1=new customer("Bat","C71828-54","222 Cat St","123-8765",60,0);
  // it is OK to do this assignment
  P2=C1;
  // because all operations allowed on P2 are meaningful for a customer
  P2->set_address("987 Dog St");
  P2->print();
  x=P2->get_name();
  // all of those are OK because even though P2 contains a customer, those
  // methods must exist for all customers

  // On the other hand, it is NOT OK to do this assignment
  C1=P1;
  // because some things we might do with a customer will be meaningless
  C1->set_rating(35);
  x=C1->get_balance();
  // those two statements are allowed because C1 was declared as a customer
  // and customers have set_rating and get_balance methods. However, the
  // variable C1 actually contains a person object, and persons do not have
  // set_rating or get_balance methods, or even rating and balance members.

Polymorphism is really easy. The true rule, for C++ is:
When one class (A) is derived from (or inherits) another class (B), it is guaranteed that an A object can do anything that a B object can do, therefore it is permitted to store pointers to A objects in variables declared as pointers to B.
In case you care, the word "polymorphism" comes from the Greek ("poly" = many) and ("morfy" = form or shape), which is surprisingly sensible for a computer term. The type of an object is considered to be its shape.

The true value of polymorphism is seen if we want to make a list of all the different people that a company is interested in. Without polymorphism, we would need a different list for each class, and it would look something like this (sticking with fixed length arrays for simplicity):
{ customer *custs[1000];
  employee *emps[1000];
  family *empfam[1000];
  stockholder *owners[100];
  ...
  custs[33]=new customer("...","...",...);
  ...
  emps[175]=new employee("...","...",...);
  ... }
We would need to define separate but almost identical functions for searching each of the four arrays, and just about every other operation. With polymorphism, we only need one array:
{ person *everyone[10000];
  ...
  everyone[33]=new customer("...","...",...);
  ...
  everyone[175]=new employee("...","...",...);
  ... }
And just one search function:
person *find(char *nm)
{ int i;
  for (i=0; iname,nm)==0)
      return (everyone[i]);
  return (NULL); }
At first sight, this search function doesn't seem too useful. It only returns a pointer-to-person, so it would be hard to use:
  person *x=find("jimmy");
          //  that's OK, but x is only a pointer-to-person. If "jimmy"
          //  happens to be a customer, we can't use x to access his
          //  credit rating or balance.
  customer *x=find("jimmy");
          //  that is not OK, because polymorphism only works in one direction.
          //  find returns a person-pointer, which can't be stored in a customer-pointer variable.
  customer *x=(customer *)find("jimmy");
          // that would work. type casting is very useful in
          // situations like this
Type casting solves a lot of the problems that arise when using polymorphism. Type casting only works reliably with simple things like ints and floats, and with pointers. When you say (customer *) find("jimmy") you are saying to the compiler "I know that find wasn't declared as returning a pointer-to-customer, but I have really checked carefully, and am sure that in this case it will do so. I take full responsibility for the consequences if I'm wrong". If only we could be sure that "jimmy" really is a customer and not an employee, everything would be fine.
        To make such certainty possible, we simply add a special little member to each person, called a tag. The tag contains a little code to indicate what kind of person we've got. Every constructor for every derived class must set that tag correctly. We can use a single character for the tag, using a scheme like 'C' for customer, 'E' for employee, 'F' for family member, 'S' for stock holder, and 'X' for unknown. The modified definitions would look something like this:
class person
{ protected:
    char tag;
    char *name, *ident, *address, *phone;
   public:
     person(char *n, char *i, char *a, char *p)
     { tag='X';
       everything else as before }
     all other methods as before };

 class customer: public person
 { protected:
     int rating, balance;
   public customer(char *n, char *i, char *a, char *p, int r, int b):
     person(n,i,a,p)
   { tag='C';
     rating=r;
     balance=b; }
   all other methods as before };

   etc etc etc
To make use of this information, we simply check the tag of a person before doing any type-casting:
{ person *temp;
  customer *cust;
  ...
  temp=find("jimmy");
  if (temp->tag=='C')
    cust=(customer *)temp;
  else
  { cust=NULL;
    print an error message }
  ...
Of course, there is nothing special about arrays. We could make a linked list in which the "thisone" pointers are of type "person *", and then we would have a mixed-type linked list working in exactly the same way.

All in all, polymorphism and inheritance can save programmers a lot of work. If we had used it for the goldfish club, the program would have been quite a lot smaller. It takes a bit more understanding, but is definitely worth the effort.