Inheritance

        Forgetting about goldfish clubs for a while (by the way, in case you got the wrong idea, I don't like goldfish at all), consider the problem of implementing classes to represent all of the different groups of people that a company has to deal with: Customers, Employees, Employees' Familiy Members, and Stock Holders. All are different, but have a lot in common (after all, they are all different kinds of people, and all people have something in common). C++ allows us to take advantage of this, by defining a class (called the Base Class) that contains only the common members and methods, and then build any number of other classes (called Derived Classes) on top of that foundation.

        First we define the base class:
class person
{ protected:
    char *name, *ident, *address, *phone;
  public:
    person(char *n, char *i, char *a, char *p);
    char *get_name(void) { return name; }
    char *get_ident(void) { return ident; }
    char *get_address(void) { return address; }
    void set_address(char *a) { address=new char[strlen(a)+1]; strcpy(address,a); }
    void set_phone(char *p) { phone=new char[strlen(p)+1]; strcpy(phone,p); }
    void print(void) { printf("Person #%s, %s of %s, %s\n", ident, name, address, phone); } };
 
person::person(char *n, char *i, char *a, char *p)
{ name=new char[strlen(n)+1]; strcpy(name,n);
  ident=new char[strlen(i)+1]; strcpy(ident,i);
  address=new char[strlen(a)+1]; strcpy(address,a);
  phone=new char[strlen(p)+1]; strcpy(phone,p); }
Now, each of the classes that we really care about can be defined simply by saying that they inherit person, and defining any new members or methods that they introduce. This is how it would look for customer:
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)
    { rating=r; balance=b; }
    int get_rating(void) { return rating; }
    void set_rating(int r) { rating=r; }
    int get_balance(void) { return balance; }
    void set_balance(int b) { balance=b; }
    void print(void)
    { printf("Customer #%s, %s of %s, %s, rating=%d, balance=%d\n",
             ident, name, address, phone, rating, balance); } };
The first line is perfectly normal except that ": public person" has been added. This simply means that the class being defined here (customer) is derived from the already known class called person. That means that all of the members and methods of person (except its constructors) are automatically copied, and will also appear as identical members and methods in this class. So the class customer has six variable members: the four strings inherited from person, and the two new integers.
        The constructor for customer needs some explaining. It has three parts: the declaration "customer(char *n, char *i, char *a, char *p, int r, int b)" which is perfectly normal, then the mysterious part: ": person(n,i,a,p)", which simply means that whenever the constructor for a customer is called, the constructor for a person should be called with the given parameters, before the specific instructions given in the third part are executed. This allows constructors to be almost inherited from the base class; you just have to say how they are called. The third part "{ rating=r; balance=b; }" is again perfectly normal; it is executed only when the call to person's constructor has done its job.
        The placement of the call to the base class constructor is quite surprising, and a person might very reasonably ask "why not just write the constructor like this:"
  customer(char *n, char *i, char *a, char *p, int r, int b)
  { person(n,i,a,p);
    rating=r; balance=b; }
The reason is one of the peculiarities of C++. Whenever an object is created, a constructor for its class must be called. As a customer is a kind of person, whenever you create a customer object, you are in a way creating a person object too. So C++ reasons that a constructor should be called for both classes on the same object. What that means is that every time you create a customer object, the system will automatically call the constructor for a person, regardless of what other work you give it in its own constructor. The only constructor it could possibly call automatically (i.e. without you having a chance to specify parameters) is the default constructor (i.e. the one that has no parameters). If you don't want the default constructor to be called (and we don't because we didn't make one), you need to tell C++ not to do it in a way that really stands out. Personally, I don't think that's a very good reason. C++ is full of peculiarities, and this is just another one to get used to.
        If we were happy to have the parameterless constructor for a person to be called automatically every time just before the constructor for a customer is called, we wouldn't have to do anything special. A perfectly normal constructor for customer, looking like this, would have sufficed:
  customer(char *n, char *i, char *a, char *p, int r, int b)
  { rating=r; balance=b; }
Of course, we couldn't be happy with that, because it would leave the four strings uninitialised.

        Everything else in the definition of customer is completely unsurprising, until we reach the print method. Person already has a print method, and customer is derived from person, so it already has a print method. Whenever you define a method with the same name and the same parameters as an inherited method, that tells C++ not to bother copying the inherited method, but to use the new one only. That's what we want there, because the print method for a person doesn't print credit ratings or account balances. It couldn't possibly do so, because many kinds of person just don't have credit ratings or account balances.


The definition of Employee will not be at all surprising now:
class customer: public person
{ protected:
    char *jobtitle, *supervisor;
    int salary;
  public:
    employee(char *n, char *i, char *a, char *p, char *j, char *s, int sal): person(n,i,a,p);
    char *get_jobtitle(void) { return jobtitle; }
    void set_jobtitle(char *j) { jobtitle=new char[strlen(j)+1]; strcpy(jobtitle,j); }
    char *get_supervisor(void) { return supervisor; }
    void set_supervisor(char *s) { supervisor=new char[strlen(s)+1]; strcpy(supervisor,s); }
    int get_salary(void) { return salary; }
    void set_salary(int s) { salary=s; }
    void give_raise(int percent) { salary=salary*(100+percent)/100; }
    void print(void)
    { printf("Employee #%s, %s of %s, %s,\n%s, supervisor %s, salary=%d\n",
             ident, name, address, phone, jobtitle, supervisor, salary); } };
 
employee::employee(char *n, char *i, char *a, char *p, char *j, char *s, int sal)
{ jobtitle=new char[strlen(j)+1]; strcpy(jobtitle,j);
  supervisor=new char[strlen(s)+1]; strcpy(supervisor,s);
  salary=sal; }
And now, the definitions of the other two classes should be clear enough for me to leave them as an exercise for the reader. I really do recommend that you do at least one of them properly (not just in your head, write it down) so that you get a proper feel for how it looks, and the few steps you have to go through. Its easy but requires a little practice.


Why does the word "public" appear before the name of the base class at the beginning of the definition of the derived class? It doesn't have to; either "protected" or "private" could appear there instead. However, "public" is the only option that ever really makes sense. When you say "class customer: public person" it means that all of person's members and methods are inherited unchanged: the public ones remain public, and the protected ones remain protected. If you said "class customer: protected person", then all the members and methods of person would still be inherited, but they would all be treated as protected. What is the point of that? There isn't a good point to it, just another of C++es peculiarities.

(The End)