6.1. Simple Derivation

[ fromfile: inheritance-intro.xml id: derivation1 ]

Inheritance is a particular way to organize classes that is supported by all object-oriented languages. It enables classes to share code in many different ways and to exploit natural relationships between classes. It can also make well-designed classes more reusable.

To employ inheritance, you place the common features of a set of related classes together in a base class and then derive other, more specialized classes from it. Each derived class inherits all the members of the base class and can override or extend each base class function as needed. Inheritance from a common base class significantly simplifes the derived classes and, with the use of certain design patterns, enables you to eliminate redundant code. In fact, inheritance sometimes suggests itself during the process of eliminating repeated blocks of code from a set of related classes.

[Tip] What's Wrong with Repeated Code?

Software that contains repeated pieces of identical or similar code is error-prone and difficult to maintain. If repeated code is allowed in a program, it can be difficult to keep track of all the repetitions.

Code often needs to be changed for one reason or another. If you need to change a piece of code that has been repeated several times, you must locate all the repetitions and apply the change to each of them, or carefully decide which of them must be changed. Chances are good that at least one repetition will be missed or that the intended change will not be applied correctly to all repetitions.

Refactoring is a process of improving the design of software, without changing its underlying behavior. One step of refactoring involves replacing blocks of similar code with calls to reusable functions.

We demonstrate inheritance with a simple example. The base class Student is supposed to contain the attributes common to all students. We kept the list of attributes short for this example, but you can easily imagine other attributes that might be appropriate.

We derived two classes from Student that describe particular kinds of students. The first derived class, Undergrad, contains only those properties specific to undergraduate students. The second derived class, GradStudent, contains only those properties specific to graduate students. The UML diagram shown in Figure 6.1 describes these relationships.

Figure 6.1.  UML Diagram of Inheritance

UML Diagram of Inheritance

The pound sign (#) that precedes Student::m_Year indicates that m_Year is a protected member of that class. Recall that protected members of a class are accessible to the member functions of derived classes. The other data members of Student are private and, hence, inaccessible to the member functions of the derived classes. The open triangle arrowhead (pointing to the base class) indicates class inheritance. That arrow is also called generalization because it points from the more specific (derived) class to the more general (base) class. The derived classes are also called subclasses of the base class.

Example 6.1 shows the definitions of the three classes.

Example 6.1. src/derivation/qmono/student.h

#ifndef STUDENT_H
#define STUDENT_H

#include <QString>

class Student  {
 public:
    Student(QString nm, long id, QString major, int year = 1);
    ~Student() {}
    QString getClassName() const; 
    QString toString() const;
 private:
    QString m_Name;
    QString m_Major;
    long m_StudentId;
 protected:
    int m_Year;
    QString yearStr() const;
};

class Undergrad: public Student {
 public:
    Undergrad(QString name, long id, QString major, int year, int sat);
    QString getClassName() const;
    QString toString() const;
 private:
    int m_SAT;  1

};

class GradStudent : public Student {
 public:
    enum Support { ta, ra, fellowship, other };
    GradStudent(QString nm, long id, QString major,
                int yr, Support support);

    QString getClassName() const ;
    QString toString() const;
 protected:
    static QString supportStr(Support sup) ;
 private:
    Support  m_Support;
};

#endif        //  #ifndef STUDENT_H

1

Scholastic Aptitude Test score total.


The classHead of each derived class specifies the base class from which it is derived and the kind of derivation used. In this case we are using public derivation. [41]

Each of the three classes has a function named getClassName() and a function named toString(). The versions of those two functions in the derived classes override the corresponding base class functions. Each such derived class function must have exactly the same signature and return type as the base class function it overrides.

Example 6.2 defines the member functions of Student.

Example 6.2. src/derivation/qmono/student.cpp

[ . . . . ]

#include <QTextStream>
#include "student.h"

Student::Student(QString nm, long id, QString major, int year)
        : m_Name(nm), m_Major(major), m_StudentId(id), m_Year(year) {}


QString Student::getClassName() const {
    return "Student";
}

QString Student::toString() const {
    QString retval;
    QTextStream os(&retval);                1
    os << "[" << getClassName() << "]" 
         << " name: " << m_Name
         << "; Id: " << m_StudentId
         << "; Year: " << yearStr()
         << "; \nMajor: " << m_Major  ;
    return retval;
}

1

Write to the stream and return its string.


The Undergrad member functions are defined in Example 6.3. It is clear that Undergrad::toString() must produce a string that contains the Student data in addition to its own data member m_SAT (the combined Scholastic Aptitude Test score).[42] Keeping in mind that the data members of Student are private, the solution to this problem is for Undergrad::toString() to call the public function, Student::toString(). Thus, encapsulation is preserved and responsibility is properly distributed: Student takes care of Student data and Undergrad takes care of Undergrad data.

Example 6.3. src/derivation/qmono/student.cpp

[ . . . . ]

Undergrad::Undergrad(QString name, long id, QString major, 
      int year, int sat)
: Student(name, id, major, year), m_SAT(sat)  1
   { }

QString Undergrad::getClassName() const {
    return "Undergrad";
}

QString Undergrad::toString() const {
    QString result;
    QTextStream os(&result);
    os <<  Student::toString()                2
       << "  [SAT: "                          3
       << m_SAT
       << " ]\n";
    return result;
}

1

The base class object is treated as a subobject of the derived object. Class members and base classes both must be initialized in an order determined by the order that they appear in the class definition.

2

Call the base class version.

3

Add items that are specific to Undergrad.


Member Initialization for Base Classes

Because each Undergrad is a Student, whenever you create an Undergrad object, you must also create and initialize a Student. Furthermore, you must call a Student constructor to initialize the Student part of any derived object.

In the member initializers of a constructor, you can treat the base class name as an implicit member of the derived class.

Look at the signature of the Undergrad constructor. The parameter list contains values for Student data members that are private, so it is not possible for an Undergrad member function to assign those values. The only way to make those assignments is to pass those values to the Student constructor, which is not private.

GradStudent has some added features that you need to handle properly, as shown in Example 6.4.

Example 6.4. src/derivation/qmono/student.cpp

[ . . . . ]

GradStudent::
GradStudent(QString nm, long id, QString major, int yr, 
                   Support support) :Student(nm, id, major, yr), 
            m_Support(support) { }

QString GradStudent::toString() const {       1
   return QString("%1%2%3 ]\n")
       .arg(Student::toString())              2
       .arg("  [Support: ")                   3 
       .arg(supportStr(m_Support));
}

1

Another QString style.

2

Call the base class version

3

Then add items that are specific to GradStudent.


Extending.  Inside both derived class versions of toString(), before the derived class attributes are handled, we explicitly call Student::toString(), which handles the (private) base class attributes. Each derived class version of toString() extends the functionality of Student::toString().

It is worth repeating that, because most of the data members of Student are private, you need a nonprivate base class function (e.g. toString()) to give the derived class access to the private base class data members. A derived class object cannot directly access the private members of Student even though it contains those members. This arrangement definitely takes some getting used to!



[41] Section 22.4 discusses the three kinds of derivation: public, protected, and private.

[42] A standardized set of exams taken by high school students and used by most college admissions offices in the United States.