Chapter 13: Inheritance

Don't hesitate to send in feedback: send an e-mail if you like the C++ Annotations; if you think that important material was omitted; if you find errors or typos in the text or the code examples; or if you just feel like e-mailing. Send your e-mail to Frank B. Brokken.

Please state the document version you're referring to, as found in the title (in this document: 7.2.2) and please state chapter and paragraph name or number you're referring to.

All received mail is processed conscientiously, and received suggestions for improvements will usually have been processed by the time a new version of the Annotations is released. Except for the incidental case I will normally not acknowledge the receipt of suggestions for improvements. Please don't interpret this as me not appreciating your efforts.

When programming in C, programming problems are commonly approached using a top-down structured approach: functions and actions of the program are defined in terms of sub-functions, which again are defined in sub-sub-functions, etc.. This yields a hierarchy of code: main() at the top, followed by a level of functions which are called from main(), etc..

In C++ the dependencies between code and data is also frequently defined in terms of dependencies among classes. This looks like composition (see section 6.4), where objects of a class contain objects of another class as their data. But the relation described here is of a different kind: a class can be defined in terms of an older, pre-existing, class. This produces a new class having all the functionality of the older class, and additionally introducing its own specific functionality. Instead of composition, where a given class contains another class, we here refer to derivation, where a given class is another class.

Another term for derivation is inheritance: the new class inherits the functionality of an existing class, while the existing class does not appear as a data member in the definition of the new class. When discussing inheritance the existing class is called the base class, while the new class is called the derived class.

Derivation of classes is often used when the methodology of C++ program development is fully exploited. In this chapter we will first address the syntactic possibilities offered by C++ for deriving classes from other classes. Then we will address some of the resulting possibilities.

As we have seen in the introductory chapter (see section 2.4), in the object-oriented approach to problem solving classes are identified during the problem analysis, after which objects of the defined classes represent entities of the problem at hand. The classes are placed in a hierarchy, where the top-level class contains the least functionality. Each new derivation (and hence descent in the class hierarchy) adds new functionality compared to yet existing classes.

In this chapter we shall use a simple vehicle classification system to build a hierarchy of classes. The first class is Vehicle, which implements as its functionality the possibility to set or retrieve the weight of a vehicle. The next level in the object hierarchy are land-, water- and air vehicles.

The initial object hierarchy is illustrated in Figure 12.

Figure 12 is shown here.
Figure 12: Initial object hierarchy of vehicles.


13.1: Related types

The relationship between the proposed classes representing different kinds of vehicles is further illustrated here. The figure shows the object hierarchy: an Auto is a special case of a Land vehicle, which in turn is a special case of a Vehicle.

The class Vehicle is thus the ` greatest common denominator' in the classification system. For the sake of the example in this class we implement the functionality to store and retrieve the vehicle's weight:

    class Vehicle
    {
        size_t d_weight;

        public:
            Vehicle();
            Vehicle(size_t weight);

            size_t weight() const;
            void setWeight(size_t weight);
    };
Using this class, the vehicle's weight can be defined as soon as the corresponding object has been created. At a later stage the weight can be re-defined or retrieved.

To represent vehicles which travel over land, a new class Land can be defined with the functionality of a Vehicle, while adding its own specific information and functionality. Assume that we are interested in the speed of land vehicles and in their weights. The relationship between Vehicles and Lands could of course be represented using composition, but that would be awkward: composition would suggest that a Land vehicle contains a vehicle, while the relationship should be that the Land vehicle is a special case of a vehicle.

A relationship in terms of composition would also needlessly bloat our code. E.g., consider the following code fragment which shows a class Land using composition (only the setWeight() functionality is shown):

    class Land
    {
        Vehicle d_v;        // composed Vehicle
        public:
            void setWeight(size_t weight);
    };

    void Land::setWeight(size_t weight)
    {
        d_v.setWeight(weight);
    }
Using composition, the setWeight() function of the class Land only serves to pass its argument to Vehicle::setWeight(). Thus, as far as weight handling is concerned, Land::setWeight() introduces no extra functionality, just extra code. Clearly this code duplication is superfluous: a Land should be a Vehicle; it should not contain a Vehicle.

The intended relationship is achieved better by inheritance. A rule of thumb for choosing between inheritance and composition distinguishes between is-a and has-a relationships. A truck is a vehicle, so Truck should probably derive from Vehicle. On the other hand, a truck has an engine; if you need to model engines in your system, you should probably express this by composing an Engine class with the Truck class.

Following the above rule, Land is derived from Vehicle, in which Vehicle is the derivation's base class:

    class Land: public Vehicle
    {
        size_t d_speed;
        public:
            Land();
            Land(size_t weight, size_t speed);

            void setspeed(size_t speed);
            size_t speed() const;
    };
By postfixing the class name Land in its definition by : public Vehicle the derivation is implemented: the class Land now contains all the functionality of its base class Vehicle plus its own specific information and functionality. The extra functionality consists of a constructor with two arguments and interface functions to access the speed data member. In the above example public derivation is used. C++ also supports private derivation and protected derivation. In section 13.6 their differences are discussed. A simple example showing the possibilities of of the derived class Land is:
    Land veh(1200, 145);

    int main()
    {
        cout << "Vehicle weighs " << veh.weight() << endl
             << "Speed is " << veh.speed() << endl;
    }
This example shows two features of derivation. First, weight() is not mentioned as a member in Land's interface. Nevertheless it is used in veh.weight(). This member function is an implicit part of the class, inherited from its ` parent' vehicle.

Second, although the derived class Land now contains the functionality of Vehicle, the private fields of Vehicle remain private: they can only be accessed by Vehicle's own member functions. This means that Land's member functions must use interface functions (like weight() and setWeight()) to address the weight field, just as any other code outside the Vehicle class. This restriction is necessary to enforce the principle of data hiding. The class Vehicle could, e.g., be recoded and recompiled, after which the program could be relinked. The class Land itself could remain unchanged.

Actually, the previous remark is not quite right: If the internal organization of Vehicle changes, then the internal organization of Land objects, containing the data of Vehicle, changes as well. This means that objects of the Land class, after changing Vehicle, might require more (or less) memory than before the modification. However, in such a situation we still don't have to worry about member functions of the parent class (Vehicle) in the class Land. We might have to recompile the Land sources, though, as the relative locations of the data members within the Land objects will have changed due to the modification of the Vehicle class.

As a rule of thumb, classes which are derived from other classes must be fully recompiled (but don't have to be modified) after changing the data organization, i.e., the data members, of their base classes. As adding new member functions to the base class doesn't alter the data organization, no recompilation is needed after adding new member functions. (A subtle point to note, however, is that adding a new member function that happens to be the first virtual member function of a class results in a new data member: a hidden pointer to a table of pointers to virtual functions. So, in this case recompilation is also necessary, as the class's data members have been silently modified. This topic is discussed further in chapter 14).

In the following example we assume that the class Auto, representing automobiles, should contain the weight, speed and name of a car. This class is conveniently derived from Land:

    class Auto: public Land
    {
        char *d_name;

        public:
            Auto();
            Auto(size_t weight, size_t speed, char const *name);
            Auto(Auto const &other);

            ~Auto();

            Auto &operator=(Auto const &other);

            char const *name() const;
            void setName(char const *name);
    };
In the above class definition, Auto is derived from Land, which in turn is derived from Vehicle. This is called nested derivation: Land is called Auto's direct base class, while Vehicle is called the indirect base class.

Note the presence of a destructor, a copy constructor and an overloaded assignment operator in the class Auto. Since this class uses a pointer to reach dynamically allocated memory, these members should be part of the class interface.

13.2: The constructor of a derived class

As mentioned earlier, a derived class inherits the functionality from its base class. In this section we shall describe the effects inheritance has on the constructor of a derived class.

As will be clear from the definition of the class Land, a constructor exists to set both the weight and the speed of an object. The poor-man's implementation of this constructor could be:

    Land::Land (size_t weight, size_t speed)
    {
        setWeight(weight);
        setspeed(speed);
    }
This implementation has the following disadvantage. The C++ compiler will generate code calling the base class's default constructor from each constructor in the derived class, unless explicitly instructed otherwise. This can be compared to the situation we encountered in composed objects (see section 6.4).

Consequently, in the above implementation the default constructor of Vehicle is called, which probably initializes the weight of the vehicle, only to be redefined immediately thereafter by the function setWeight().

A more efficient approach is of course to call the constructor of Vehicle expecting a size_t weight argument directly. The syntax achieving this is to mention the constructor to be called (supplied with its arguments) immediately following the argument list of the constructor of the derived class itself. Such a base class initializer is shown in the next example. Following the constructor's head a colon appears, which is then followed by the base class constructor. Only then any member initializer may be specified (using commas to separate multiple initializers), followed by the constructor's body:

    Land::Land(size_t weight, size_t speed)
    :
        Vehicle(weight)
    {
        setspeed(speed);
    }

13.3: The destructor of a derived class

Destructors of classes are automatically called when an object is destroyed. This also holds true for objects of classes derived from other classes. Assume we have the following situation:
    class Base
    {
        public:
            ~Base();
    };

    class Derived: public Base
    {
        public:
            ~Derived();
    };

    int main()
    {
        Derived
            derived;
    }
At the end of the main() function, the derived object ceases to exists. Hence, its destructor (~Derived()) is called. However, since derived is also a Base object, the ~Base() destructor is called as well. It is not neccessary to call the base class destructor explicitly from the derived class destructor.

Constructors and destructors are called in a stack-like fashion: when derived is constructed, the appropriate base class constructor is called first, then the appropriate derived class constructor is called. When the object derived is destroyed, its destructor is called first, automatically followed by the activation of the Base class destructor. A derived class destructor is always called before its base class destructor is called.

13.4: Redefining member functions

The functionality of all members of a base class (which are therefore also available in derived classes) can be redefined. This feature is illustrated in this section.

Let's assume that the vehicle classification system should be able to represent trucks, consisting of two parts: the front engine, pulling the second part, a trailer. Both the front engine and the trailer have their own weights, and the weight() function should return the combined weight.

The definition of a Truck therefore starts with the class definition, derived from Auto but it is then expanded to hold one more size_t field representing the additional weight information. Here we choose to represent the weight of the front part of the truck in the Auto class and to store the weight of the trailer in an additional field:

    class Truck: public Auto
    {
        size_t d_trailer_weight;

        public:
            Truck();
            Truck(size_t engine_wt, size_t speed, char const *name,
                  size_t trailer_wt);

            void setWeight(size_t engine_wt, size_t trailer_wt);
            size_t weight() const;
    };

    Truck::Truck(size_t engine_wt, size_t speed, char const *name,
                 size_t trailer_wt)
    :
        Auto(engine_wt, speed, name)
    {
        d_trailer_weight = trailer_wt;
    }
Note that the class Truck now contains two functions already present in the base class Auto: setWeight() and weight(). The next example shows the actual use of the member functions of the class Truck, displaying several weights:
    int main()
    {
        Land veh(1200, 145);
        Truck lorry(3000, 120, "Juggernaut", 2500);

        lorry.Vehicle::setWeight(4000);

        cout << endl << "Truck weighs " <<
                        lorry.Vehicle::weight() << endl <<
            "Truck + trailer weighs " << lorry.weight() << endl <<
            "Speed is " << lorry.speed() << endl <<
            "Name is " << lorry.name() << endl;
    }
Note the explicit call of Vehicle::setWeight(4000): assuming setWeight(size_t engine_wt) is not part of the interface of the class Truck, it must be called explicitly using the Vehicle:: scope resolution, as the single argument function setWeight() is hidden from direct view in the class Truck.

With Vehicle::weight() and Truck::weight() the situation is somewhat different: here the function Truck::weight() is a redefinition of Vehicle::weight(), so in order to reach Vehicle::weight() a scope resolution operation (Vehicle::) is required.

13.5: Multiple inheritance

Up to now, a class was always derived from a single base class. C++ also supports multiple derivation, in which a class is derived from several base classes and hence inherits functionality of multiple parent classes at the same time. In cases where multiple inheritance is considered, it should be defensible to consider the newly derived class an instantiation of both base classes. Otherwise, composition might be more appropriate. In general, linear derivation, in which there is only one base class, is used much more frequently than multiple derivation. Most objects have a primary purpose, and that's it. But then, consider the prototype of an object for which multiple inheritance was used to its extreme: the Swiss army knife! This object is a knife, it is a pair of scissors, it is a can-operner, it is a corkscrew, it is ....

How can we construct a `Swiss army knife' in C++? First we need (at least) two base classes. For example, let's assume we are designing a toolkit allowing us to construct an instrument panel of an aircraft's cockpit. We design all kinds of instruments, like an artificial horizon and an altimeter. One of the components that is often seen in aircraft is a nav-com set: a combination of a navigational beacon receiver (the `nav' part) and a radio communication unit (the `com'-part). To define the nav-com set, we first design the NavSet class. For the time being, its data members are omitted:

    class NavSet
    {
        public:
            NavSet(Intercom &intercom, VHF_Dial &dial);

            size_t activeFrequency() const;
            size_t standByFrequency() const;

            void setStandByFrequency(size_t freq);
            size_t toggleActiveStandby();
            void setVolume(size_t level);
            void identEmphasis(bool on_off);
    };
In the class's contructor we assume the availability of the classes Intercom, which is used by the pilot to listen to the information transmitted by the navigational beacon, and a class VHF_Dial which is used to represent visually what the NavSet receives.

Next we construct the ComSet class. Again, omitting the data members:

    class ComSet
    {
        public:
            ComSet(Intercom &intercom);

            size_t frequency() const;
            size_t passiveFrequency() const;

            void setPassiveFrequency(size_t freq);
            size_t toggleFrequencies();

            void setAudioLevel(size_t level);
            void powerOn(bool on_off);
            void testState(bool on_off);
            void transmit(Message &message);
    };
Using objects of this class we can receive messages, transmitted though the Intercom, but we can also transmit messages using a Message object that's passed to the ComSet object using its transmit() member function.

Now we're ready to construct the NavCom set:

    class NavComSet: public ComSet, public NavSet
    {
        public:
            NavComSet(Intercom &intercom, VHF_Dial &dial);
    };
Done. Now we have defined a NavComSet which is both a NavSet and a ComSet: the possibilities of either base class are now available in the derived class using multiple derivation.

With multiple derivation, please note the following:

Of course, while defining the base classes, we made life easy on ourselves by strictly using different member function names. So, there is a function setVolume() in the NavSet class and a function setAudioLevel() in the ComSet class. A bit cheating, since we could expect that both units in fact have a composed object Amplifier, handling the volume setting. A revised class might then either use a Amplifier &amplifier() const member function, and leave it to the application to set up its own interface to the amplifier, or access functions for, e.g., the volume are made available through the NavSet and ComSet classes as, normally, member functions having the same names (e.g., setVolume()). In situations where two base classes use the same member function names, special provisions need to be made to prevent ambiguity:

13.6: Public, protected and private derivation

As we've seen, classes may be derived from other classes using inheritance. Usually the derivation type is public, implying that the access rights of the base class's interface is unaltered in the derived class.

Apart from public derivation, C++ also supports protected derivation and private derivation

To use protected derivation. the keyword protected is specified in the inheritance list:

    class Derived: protected Base
When protected derivation is used all the base class's public and protected members turn into protected members in the derived class. Members having protected access rights are available to the class itself and to all classes that are (directly or indirectly) derived from it.

To use private derivation. the keyword private is specified in the inheritance list:

    class Derived: private Base
When private derivation is used all the base class's members turn into private members in the derived class. Members having private access rights are only available to the class itself.

Combinations of inheritance types do occur. For example, when designing a stream-class it is usually derived from std::istream or std::ostream. However, before a stream can be constructed, a std::streambuf must be available. Taking advantage of the fact that the inheritance order is taken seriously by the compiler, we can use multiple inheritance (see section 13.5) to derive the class from both std::streambuf and (then) from, e.g., std::ostream. As our class faces its clients as a std::ostream and not as a std::streambuf, we use private derivation for the latter, and public derivation for the former class:

    class Derived: private std::streambuf, public std::ostream

13.6.1: Promoting access rights

When private or protected derivation is used, users of derived class objects are denied access to the base class members. Private derivation denies access of all base class members to users of the derived class, protected derivation does the same, but allows classes that are in turn derived from the derived class to access the base class's public and protected members.

In some situations this scheme is too restrictive. Consider a class RandStream derived privately from a class RandBuf which is itself derived from std::streambuf and publicly from istream:

    class RandBuf: public std::streambuf
    {
        // implements a buffer for random numbers
    };
    class RandStream: private RandBuf, public std::istream
    {
        // implements a stream to extract random values from
    };
Such a class could be used to extract, e.g., random numbers using the standard istream interface.

Although the RandStream class is constructed with the functionality of istream objects in mind, some of the members of the class std::streambuf may be considered useful by themselves. E.g., the function streambuf::in_avail() returns a lower bound on the number of characters that can be read immediately. The standard way to make this function available is to define a shadow member calling the base class's member:

    class RandStream: private RandBuf, public std::istream
    {
        // implements a stream to extract random values from
        public:
            std::streamsize in_avail();
    };
    inline std::streamsize RandStream::in_avail()
    {
        return std::streambuf::in_avail();
    }
This looks like a lot of work for just making available a member from the protected or private base classes. If the intent is to make available the in_avail member access promotion can be used: by declaring the protected or private base class in the public interface that specific base class member becomes available to the class's users. Here is the above example, now using access promotion:
    class RandStream: private RandBuf, public std::istream
    {
        // implements a stream to extract random values from
        public:
            using std::streambuf::in_avail;
    };
It should be noted that access promotion makes available all overloaded versions of the declared base class member. So, if streambuf would offer not only in_avail() but also, e.g., in_avail(size_t *) both members would become part of the public interface.

13.7: Conversions between base classes and derived classes

When inheritance is used to define classes, it can be said that an object of a derived class is at the same time an object of the base class. This has important consequences for the assignment of objects, and for the situation where pointers or references to such objects are used. Both situations will be discussed next.

13.7.1: Conversions in object assignments

Continuing our discussion of the NavCom class, introduced in section 13.5 We start by defining two objects, a base class and a derived class object:
    ComSet com(intercom);
    NavComSet navcom(intercom2, dial2);
The object navcom is constructed using an Intercom and a Dial object. However, a NavComSet is at the same time a ComSet, allowing the assignment from navcom (a derived class object) to com (a base class object):
    com = navcom;
The effect of this assignment should be that the object com will now communicate with intercom2. As a ComSet does not have a VHF_Dial, the navcom's dial is ignored by the assignment: when assigning a base class object from a derived class object only the base class data members are assigned, other data members are ignored.

The assignment from a base class object to a derived class object, however, is problematic: In a statement like

    navcom = com;
it isn't clear how to reassign the NavComSet's VHF_Dial data member as they are missing in the ComSet object com. Such an assignment is therefore refused by the compiler. Although derived class objects are also base class objects, the reverse does not hold true: a base class object is not also a derived class object.

The following general rule applies: in assignments in which base class objects and derived class objects are involved, assignments in which data are dropped is legal. However, assignments in which data would remain unspecified is not allowed. Of course, it is possible to redefine an overloaded assignment operator to allow the assignment of a derived class object by a base class object. E.g., to achieve compilability of a statement

    navcom = com;
the class NavComSet must have an overloaded assignment operator function accepting a ComSet object for its argument. It would be the responsibility of the programmer constructing the assignment operator to decide what to do with the missing data.

13.7.2: Conversions in pointer assignments

We return to our Vehicle classes, and define the following objects and pointer variable:
    Land land(1200, 130);
    Auto auto(500, 75, "Daf");
    Truck truck(2600, 120, "Mercedes", 6000);
    Vehicle *vp;
Now we can assign the addresses of the three objects of the derived classes to the Vehicle pointer:
    vp = &land;
    vp = &auto;
    vp = &truck;
Each of these assignments is acceptable. However, an implicit conversion of the derived class to the base class Vehicle is used, since vp is defined as a pointer to a Vehicle. Hence, when using vp only the member functions manipulating weight can be called as this is the Vehicle's only functionality. As far as the compiler can tell this is the object vp points to.

The same reasoning holds true for references to Vehicles. If, e.g., a function is defined having a Vehicle reference parameter, the function may be passed an object of a class derived from Vehicle. Inside the function, the specific Vehicle members remain accessible. This analogy between pointers and references holds true in general. Remember that a reference is nothing but a pointer in disguise: it mimics a plain variable, but actually it is a pointer.

This restricted functionality furthermore has an important consequence for the class Truck. After the statement vp = &truck, vp points to a Truck object. So, vp->weight() will return 2600 instead of 8600 (the combined weight of the cabin and of the trailer: 2600 + 6000), which would have been returned by truck.weight().

When a function is called using a pointer to an object, then the type of the pointer (and not the type of the object) determines which member functions are available and executed. In other words, C++ implicitly converts the type of an object reached through a pointer to the pointer's type.

If the actual type of the object to which a pointer points is known, an explicit type cast can be used to access the full set of member functions that are available for the object:

    Truck truck;
    Vehicle *vp;

    vp = &truck;        // vp now points to a truck object

    Truck *trp;

    trp = reinterpret_cast<Truck *>(vp);
    cout << "Make: " << trp->name() << endl;
Here, the second to last statement specifically casts a Vehicle * variable to a Truck *. As is usually the case with type casts, this code is not without risk: it will only work if vp really points to a Truck. Otherwise the program may behave unexpectedly.

13.8: Using non-default constructors with new[]

An often heard source of irritation is the fact that operator new[] calls the default constructor of a class to initialize the allocated objects. For example, to allocate an array of 10 strings we can do
    new string[10];
but it is not possible to use another constructor. Assuming that we'd want to initialize the strings with the text hello world, we can't write something like:
    new string("hello world")[10];

Such an initialization is usually accomplished in a two-step process: first the array is allocated (implicitly calling the default constructor); second the array's elements are initialized, as in the following little example:

    string *sp = new string[10];
    fill(sp, sp + 10, string("hello world"));
These approaches all suffer from `double initializations', comparable to not using member initializers in constructors.

Fortunately inheritance can profitably be used to call non-default constructors in combination with operator new[]. The approach capitalizes on the following:

The above also suggest the prototypical form of the approach:

Here is a simple example, producing 10 lines containing the text hello world:
#include <iostream>
#include <string>
#include <algorithm>
#include <iterator>

using namespace std;

struct Xstr: public string
{
    Xstr()
    :
        string("hello world")
    {}
};

int main()
{
    string *sp = new Xstr[10];
    copy(sp, sp + 10, ostream_iterator<string>(cout, "\n"));
}
Of course, the above example is fairly unsophisticated, but it's easy to polish the example: the class Xstr can be defined in an anonymous namespace, accessible only to a function getString() which may be given a size_t nObjects parameter, allowing users to specify the number of hello world-initialized strings they would like to allocate.

Instead of hard-coding the base class arguments it's also possible to use variables or functions providing the appropriate values for the base class constructor's arguments. In the next example a local class Xstr is defined inside a function nStrings(size_t nObjects, char const *fname), expecting the number of string objects to allocate and the name of a file whose subsequent lines are used to initialize the objects. The local class is invisible outside of the function nStrings, so no special namespace safeguards are required.

As discussed in section 6.5, members of local classes cannot access local variables from their surrounding function. However, they can access global and static data defined by the surrounding function.

Using a local class neatly allows us to hide the implementation details within the function nStrings, which simply opens the file, allocates the objects, and closes the file again. Since the local class is derived from string, it can use any string constructor for its base class initializer. In this particular case it calls the string(char const *) constructor, providing it with subsequent lines of the just opened stream via its static member function nextLine(). This latter function is, as it is a static member function, available to Xstr default constructor's member initializers even though no Xstr object is available by that time.

#include <fstream>
#include <iostream>
#include <string>
#include <algorithm>
#include <iterator>

using namespace std;

string *nStrings(size_t size, char const *fname)
{
    static ifstream in;

    struct Xstr: public string
    {
        Xstr()
        :
            string(nextLine())
        {}
        static char const *nextLine()
        {
            static string line;

            getline(in, line);
            return line.c_str();
        }
    };
    in.open(fname);
    string *sp = new Xstr[10];
    in.close();

    return sp;
}

int main()
{
    string *sp = nStrings(10, "nstrings.cc");
    copy(sp, sp + 10, ostream_iterator<string>(cout, "\n"));
}
When this program is run, it displays the first 10 lines of the file nstrings.cc.

Note that the above implementation can't safely be used in a multithreaded environment. In that case a mutex should be used to protect the three statements just before the function's return statement.