Class Inheritance

Read time: 28 minutes (7064 words)

So far, all of our classes have been self standing. That means that they depended on no other class to do their work. The world we live in is not structured like that! We have objects that are similar in some ways and different in others. A common example of this is the hierarchy of living critters:

Living
    |
    + -- animal
    |       |
    |       |
    |       + - vertrbrate)
    |       |       |
    |       |       + - Mammal
    |       |       |
    |       |       + - Birds
    |       |       |
    |       |       + - Fish
    |       |       |
    |       |       + - Amphibians
    |       |       |
    |       |       + - Reptiles
    |       |
    |       + - invertebrate)
    |               |
    |               + - Mollucs
    |               |
    |               + - Insect
    |               |
    |               + - Arachnid
    |
    + - plant
            |
            + - Flowering
            |
            + - Non-Flowering

Obviously, this is an abbreviated chart for all living critters, but it sets us up to discuss a new concept.

Each branch of this chart adds features that are a specialization of the level above it. Some set of attributes of a living being distinguishes one kind of living object from another. As you dive deeper into the chart, new attributes are introduced that specialize the objects even further.

A chart of this type is called an inheritance chart, since every level inherits the attributes of the level above it.

A simpler inheritance chart might be constructed for vehicles:

vehicle
    |
    + - Rolls
    |     |
    |     + - Bycycle
    |     |
    |     + - Motorcycle
    |     |
    |     + - Automobile
    |     |
    |     + - Truck
    |            |
    |            + - Pickup
    |            |
    |            + - Semi
    |
    + - Floats
    |     |
    |     + - Sailboat
    |     |
    |     + - Powerboat
    |
    + - Flys
          |
          + - glider
          |
          + - Powered

Once again, you should be able to list attributes that every object of a certain type has, and you add specialization attributes as you move deeper into the chart.

Creating Classes by Inheritance

What might not be so obvious is that the behaviors of objects are similar as well, but as we specialize our objects we might need to introduce new behaviors to properly define how each object works.

We can collect the set of common attributes and behaviors in a class, then inherit those attributes and behaviors in a new class where we add specialization attributes and new behaviors. This lets us simplify the writing of a complex set of classes to handle a wide variety of similar objects.

One of the more common places where inheritance is used is in graphics programming. Defining basic shaped to be drawn on a screen is an obvious thing to do, and we can build specialized versions of drawing methods for particular kinds of shapes. One of these days, I will rewrite our Graphics library to be more object-oriented!

Multiple Inheritance

One of C++’s more controversial features is support for deriving a class from more than one base class. This is called “multiple inheritance”, and can get quite complicated to manage properly. That is a topic for another class!

To keep things simple in our example code, let’s stick with what we know from school!

People in School

Let’s start by defining a class called Person:

Person.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#pragma once 
#include <string>
#include "school_types.h"

class Person {
    public:
        std::string name;
        std::string address;

        // constructors
        Person();
        Person(std::string n, std::string add);

    private:
        int birthday[3];
        std::string ACCID;

        // accessors
        std::string getID(void);

        int getBirthYear(void);
        int getBirthMonth(void);
        int getBirthDay(void);

        // mutators
        void setID(std::string ID);
        void setBirthday(int year, int month, int day);
};

This class contains attributes that everyone in school has, like a name and address. Since we are working on a program that only deals with folks who are associated with the school, we will give every person an ID number as well.

Notice that we have a constructor defined for a Person here. Unfortunately, we will need to build constructors for derived classes as well. Furthermore, you will need to initialize all of the attributes available in the derived class, including those in the base class. Other languages, including Java and Python support the notion of a constructor calling a special method called super to handle initializing the base class attributes. In C++, you need to call the base class constructor explicitly using notation that names the base class. It looks something like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class Base {
    public:
        Base(int x);
};
    
class Child : public Base {
    public:
        Child(int x, int y) : Base(x) {
            work with y here
        }
};

Notice that we pass on the parameter actually needed in the base class when we set up the constructor for the derived class. This is messy enough that we will not use it in our example code.

Deriving from Person

With this base class, we are ready to create specialized people called “students” and “faculty”. We will build a class for each of these special people, and inherit the common attributes and behaviors from the base class.

The process is simple, we will only add the new attributes and methods:

Student.h:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#pragma once
#include <string>
#include "Person.h"
#include "school_types.h"
    
class Student : public Person {

    public:
        // constructor
        Student(std::string n, std::string add, Discipline d, Person *adv);

        // accessors
        Discipline getMajor(void) { return major; }
        Person * getAdvisor() { return advisor; } 
        
    private:
        Discipline major;
        Person * advisor;

        // mutators
        void setMajor(Discipline d) { major = d; }
        void setAdvisor(Person * name) { advisor = name; } 
};

Here is the required implementation file:

Student.cpp:
1
2
3
4
5
6
7
8
9
#include <string>
#include "Student.h"
    
Student::Student(std::string n, std::string add, Discipline d, Person * adv) { name = n;
    address = add;
    major = d;
    advisor = adv;
}

Here is the Faculty class, which we keep pretty simple:

Faculty.h:
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
#pragma once    
#include <string>
#include "Person.h"
#include "school_types.h"
    
class Faculty : public Person {
    public:
        // constructor
        Faculty(std::string n, std::string add);
        
        // accessors
        Discipline getDepartment(void);
    
        // mutators
        void setDepartment(Discipline d);

    private:
        Discipline department;
};

And the implementation:

Faculty.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
#include "Faculty.h"
#include "school_types.h"

// Constructor
Faculty::Faculty(std::string n, std::string add) {
    name = n;
    address = add; 
}
        
// accessors
Discipline Faculty::getDepartment(void) {
    return department; 
}
    
// mutators
void Faculty::setDepartment(Discipline d) {
    department = d; 
}

In each case, the new attributes we have added use something called an “enumeration” which we collect in a common include file:

school_types.h:
1
2
3
4
5
#pragma once

enum Discipline {COMPUTER_SCIENCE, ENGINEERING, MEDICAL};
enum Classification {FRESHMAN, SOPHOMORE};

An enumeration is just a list of names (not variables). These names end up acting like integers with values derived from the sequence of unsigned integer numbers. The use of such an enumeration makes your code more readable, but we will have to work to print the names out.

Demonstrating the classes

We need an example program to test out our new classes:

main.cpp
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// main.cpp - test program for inheritance example
#include <iostream>
#include <string>
#include "school_types.h"
#include "Faculty.h"
#include "Student.h"
#include "Person.h"

const std::string dName[] = {
    "Computer Science", "Engineering", "Medical" 
};

const std::string cName[] = {
    "Freshman", "Sophomore" 
};

int main(int argc, char ** argv) {
    std::cout << "Testing Inheritance" << std::endl;

    Faculty professor("Roie Black", "123 Main Street");
    professor.setDepartment(COMPUTER_SCIENCE);

    Student stu("Fred Flintstone", "123 Bedrock", COMPUTER_SCIENCE, &professor);

    std::cout << "Professor: " << professor.name << std::endl;
    std::cout << "\t" << dName[professor.getDepartment()] << std::endl;

    std::cout << "Student: " << stu.name << std::endl;
    std::cout << "\t" << cName[stu.getMajor()] << std::endl;
    
    Person * stuAdvisor = stu.getAdvisor();
    std::cout << "\tAdvisor: " << stuAdvisor->name << std::endl;
    
    return 0;
}

In this file, we set up arrays of strings for the enumerations we have defined. These strings are only needed if you want to print out the enumeration values. Since they act like integers, you can use them as indices in an array, and that is seen in the above code where we print out the student major, and the faculty department.

Here is a Makefile to build the example program. It uses features we went over when we studied Makefile dependencies.

Makefile
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
# Makefile for Inheritance example

TARGET  = inheritance

FILES   = $(wildcard *.cpp)
OBJS    = $(FILES:.cpp=.o)
CPPFLAGS	+= -MMD -MP
CPP		= g++
RM		= rm -f

all:    $(TARGET)
    
$(TARGET):	$(OBJS)
	$(CPP) $(CPPFLAGS) -o $@ $^
    
%.o:    %.cpp
	g++ $(CPPFLAGS) -c -o $@ $<
    
clean:
	$(RM) $(OBJS) *.d $(TARGET)
    
-include $(FILES:%.cpp=%.d)

Here is a sample run of the program to show how things work:

rm -f Faculty.o Person.o Student.o main.o *.d inheritance
make[1]: *** No rule to make target `&&'.  Stop.

System Message: ERROR/6 (/Users/rblack/_projects/lectures/cosc1337/lectures/20-Polymorphism/01-inheritance.rst, line 221)

Command './inheritance' failed: [Errno 2] No such file or directory: './inheritance'

Why use Inheritance

The whole point of object-oriented programming is helping us keep our code well organized, and keep things together that belong together. We can protect attributes and behaviors we deem unnecessary for normal users to access, and make public only what we need to as part of keeping our code secure.

Inheritance collects common code that otherwise would have to be duplicated in each derived class, and we know that can lead to errors. Remember the mantra:

DRY - Don’t Repeat Yourself

I must admit that I have done a lot of OOP without using inheritance that much. The need for this capability depends on the kind of problem you are trying to solve, but it there to help when you need it, or find it useful to keep your code clean and easy to test (remember that?) Controlling Access in Derived Classes

Did you notice that we used “public Person” when we created both the Faculty and Student classes?

We have seen the use of “public” and “private” used to control access to attributes and methods in classes before. There is actually yet another level of control: “protected”. Protected members

We can set up “protected” members of a base class, and use this control to allow derived classes to access the protected members.

Protected access gives you the power to allow a derived class to access a base class attribute, but restrict whether they can modify that attribute. Derived Class Access Rules

The rules for setting up all the access controls are a bit confusing, and can be safely ignored for a lot of your work. However, here they are for future reference:

Base Class

Derived type

Derived class access

Private members

Private base class

Inaccessible

Protected members

Private members

Public members

Private members

Private members

protected base class

Inaccessible

Protected members

Protected members

Public members

Protected members

Private members

public base class

Inaccessible

Protected members

Protected members

Public members

Public members

This is a lot to remember, so keep a good reference handy when you do real C++ programming for future projects.

By the way, I am currently reading one of Bjarne Stroustrup’s textbooks on C++, one that he uses in his beginning C++ courses, titled “Programming Principles and Practice Using C++”. It is a good reference. Far better are his books on the language itself. If you expect to do much C++ programming, I recommend getting a copy of the most current version of his text.