Programming in D – Tutorial and Reference
Ali Çehreli

Other D Resources

Inheritance

Inheritance is defining a more specialized type based on an existing more general base type. The specialized type acquires the members of the base type and as a result can be substituted in place of the base type.

Inheritance is available for classes, not structs. The class that inherits another class is called the subclass, and the class that gets inherited is called the superclass, also called the base class.

There are two types of inheritance in D. We will cover implementation inheritance in this chapter and leave interface inheritance to a later chapter.

When defining a subclass, the superclass is specified after a colon character:

class SubClass : SuperClass {
    // ...
}

To see an example of this, let's assume that there is already the following class that represents a clock:

class Clock {
    int hour;
    int minute;
    int second;

    void adjust(int hour, int minute, int second = 0) {
        this.hour = hour;
        this.minute = minute;
        this.second = second;
    }
}

Apparently, the members of that class do not need special values during construction; so there is no constructor. Instead, the members are set by the adjust() member function:

    auto deskClock = new Clock;
    deskClock.adjust(20, 30);
    writefln(
        "%02s:%02s:%02s",
        deskClock.hour, deskClock.minute, deskClock.second);

Note: It would be more useful to produce the time string by a toString() function. It will be added later when explaining the override keyword below.

The output:

20:30:00

With only that much functionality, Clock could be a struct as well, and depending on the needs of the program, that could be sufficient.

However, being a class makes it possible to inherit from Clock.

To see an example of inheritance, let's consider an AlarmClock that not only includes all of the functionality of Clock, but also provides a way of setting the alarm. Let's first define this type without regard to Clock. If we did that, we would have to include the same three members of Clock and the same adjust() function that adjusted them. AlarmClock would also have other members for its additional functionality:

class AlarmClock {
    int hour;
    int minute;
    int second;
    int alarmHour;
    int alarmMinute;

    void adjust(int hour, int minute, int second = 0) {
        this.hour = hour;
        this.minute = minute;
        this.second = second;
    }

    void adjustAlarm(int hour, int minute) {
        alarmHour = hour;
        alarmMinute = minute;
    }
}

The members that appear exactly in Clock are highlighted. As can be seen, defining Clock and AlarmClock separately results in code duplication.

Inheritance is helpful in such cases. Inheriting AlarmClock from Clock simplifies the new class and reduces code duplication:

class AlarmClock : Clock {
    int alarmHour;
    int alarmMinute;

    void adjustAlarm(int hour, int minute) {
        alarmHour = hour;
        alarmMinute = minute;
    }
}

The new definition of AlarmClock is the equivalent of the previous one. The highlighted part of the new definition corresponds to the highlighted parts of the old definition.

Because AlarmClock inherits the members of Clock, it can be used just like a Clock:

    auto bedSideClock = new AlarmClock;
    bedSideClock.adjust(20, 30);
    bedSideClock.adjustAlarm(7, 0);

The members that are inherited from the superclass can be accessed as if they were the members of the subclass:

    writefln("%02s:%02s:%02s ♫%02s:%02s",
             bedSideClock.hour,
             bedSideClock.minute,
             bedSideClock.second,
             bedSideClock.alarmHour,
             bedSideClock.alarmMinute);

The output:

20:30:00 ♫07:00

Note: An AlarmClock.toString function would be more useful in this case. It will be defined later below.

The inheritance used in this example is implementation inheritance.

If we imagine the memory as a ribbon going from top to bottom, the placement of the members of AlarmClock in memory can be pictured as in the following illustration:

                            │      .      │
                            │      .      │
the address of the object → ├─────────────┤
                            │(other data) │
                            │ hour        │
                            │ minute      │
                            │ second      │
                            │ alarmHour   │
                            │ alarmMinute │
                            ├─────────────┤
                            │      .      │
                            │      .      │

The illustration above is just to give an idea on how the members of the superclass and the subclass may be combined together. The actual layout of the members depends on the implementation details of the compiler in use. For example, the part that is marked as other data typically includes the pointer to the virtual function table (vtbl) of that particular class type. The details of the object layout are outside the scope of this book.

Warning: Inherit only if "is a"

We have seen that implementation inheritance is about acquiring members. Consider this kind of inheritance only if the subtype can be thought of being a kind of the supertype as in the phrase "alarm clock is a clock."

"Is a" is not the only relationship between types; a more common relationship is the "has a" relationship. For example, let's assume that we want to add the concept of a Battery to the Clock class. It would not be appropriate to add Battery to Clock by inheritance because the statement "clock is a battery" is not true:

class Clock : Battery {    // ← WRONG DESIGN
    // ...
}

A clock is not a battery; it has a battery. When there is such a relationship of containment, the type that is contained must be defined as a member of the type that contains it:

class Clock {
    Battery battery;       // ← Correct design
    // ...
}
Inheritance from at most one class

Classes can only inherit from a single base class (which itself can potentially inherit from another single class). In other words, multiple inheritance is not supported in D.

For example, assuming that there is also a SoundEmitter class, and even though "alarm clock is a sound emitting object" is also true, it is not possible to inherit AlarmClock both from Clock and SoundEmitter:

class SoundEmitter {
    // ...
}

class AlarmClock : Clock, SoundEmitter {    // ← compilation ERROR
    // ...
}

On the other hand, there is no limit to the number of interfaces that a class can inherit from. We will see the interface keyword in a later chapter.

Additionally, there is no limit to how deep the inheritance hierarchy can go:

class MusicalInstrument {
    // ...
}

class StringInstrument : MusicalInstrument {
    // ...
}

class Violin : StringInstrument {
    // ...
}

The inheritance hierarchy above defines a relationship from the more general to the more specific: musical instrument, string instrument, and violin.

Hierarchy charts

Types that are related by the "is a" relationship form a class hierarchy.

According to OOP conventions, class hierarchies are represented by superclasses being on the top and the subclasses being at the bottom. The inheritance relationships are indicated by arrows pointing from the subclasses to the superclasses.

For example, the following can be a hierarchy of musical instruments:

             MusicalInstrument
                ↗         ↖
    StringInstrument   WindInstrument
         ↗    ↖            ↗    ↖
     Violin  Guitar    Flute   Recorder
Accessing superclass members

The super keyword allows referring to members that are inherited from the superclass.

class AlarmClock : Clock {
    // ...

    void foo() {
        super.minute = 10; // The inherited 'minute' member
        minute = 10;       // Same thing if there is no ambiguity
    }
}

The super keyword is not always necessary; minute alone has the same meaning in the code above. The super keyword is needed when both the superclass and the subclass have members under the same names. We will see this below when we will need to write super.reset() and super.toString().

If multiple classes in an inheritance tree define a symbol with the same name, one can use the specific name of the class in the inheritance tree to disambiguate between the symbols:

class Device {
    string manufacturer;
}

class Clock : Device {
    string manufacturer;
}

class AlarmClock : Clock {
    // ...

    void foo() {
        Device.manufacturer = "Sunny Horology, Inc.";
        Clock.manufacturer = "Better Watches, Ltd.";
    }
}
Constructing superclass members

The other use of the super keyword is to call the constructor of the superclass. This is similar to calling the overloaded constructors of the current class: this when calling constructors of the current class and super when calling constructors of the superclass.

It is not required to call the superclass constructor explicitly. If the constructor of the subclass makes an explicit call to any overload of super, then that constructor is executed by that call. Otherwise, and if the superclass has a default constructor, it is executed automatically before entering the body of the subclass.

We have not defined constructors for the Clock and AlarmClock classes yet. For that reason, the members of both of those classes are initialized by the .init values of their respective types, which is 0 for int.

Let's assume that Clock has the following constructor:

class Clock {
    this(int hour, int minute, int second) {
        this.hour = hour;
        this.minute = minute;
        this.second = second;
    }

    // ...
}

That constructor must be used when constructing Clock objects:

    auto clock = new Clock(17, 15, 0);

Naturally, the programmers who use the Clock type directly would have to use that syntax. However, when constructing an AlarmClock object, they cannot construct its Clock part separately. Besides, the users of AlarmClock need not even know that it inherits from Clock.

A user of AlarmClock should simply construct an AlarmClock object and use it in the program without needing to pay attention to its Clock heritage:

    auto bedSideClock = new AlarmClock(/* ... */);
    // ... use as an AlarmClock ...

For that reason, constructing the superclass part is the responsibility of the subclass. The subclass calls the constructor of the superclass with the super() syntax:

class AlarmClock : Clock {
    this(int hour, int minute, int second,  // for Clock's members
         int alarmHour, int alarmMinute) {  // for AlarmClock's members
        super(hour, minute, second);
        this.alarmHour = alarmHour;
        this.alarmMinute = alarmMinute;
    }

    // ...
}

The constructor of AlarmClock takes arguments for both its own members and the members of its superclass. It then uses part of those arguments to construct its superclass part.

Overriding the definitions of member functions

One of the benefits of inheritance is being able to redefine the member functions of the superclass in the subclass. This is called overriding: The existing definition of the member function of the superclass is overridden by the subclass with the override keyword.

Overridable functions are called virtual functions. Virtual functions are implemented by the compiler through virtual function pointer tables (vtbl) and vtbl pointers. The details of this mechanism are outside the scope of this book. However, it must be known by every system programmer that virtual function calls are more expensive than regular function calls. Every non-private class member function in D is virtual by default. For that reason, when a superclass function does not need to be overridden at all, it should be defined as final so that it is not virtual. We will see the final keyword later in the Interfaces chapter.

Let's assume that Clock has a member function that is used for resetting its members all to zero:

class Clock {
    void reset() {
        hour = 0;
        minute = 0;
        second = 0;
    }

    // ...
}

That function is inherited by AlarmClock and can be called on an AlarmClock object:

    auto bedSideClock = new AlarmClock(20, 30, 0, 7, 0);
    // ...
    bedSideClock.reset();

However, necessarily ignorant of the members of AlarmClock, Clock.reset can only reset its own members. For that reason, to reset the members of the subclass as well, reset() must be overridden:

class AlarmClock : Clock {
    override void reset() {
        super.reset();
        alarmHour = 0;
        alarmMinute = 0;
    }

    // ...
}

The subclass resets only its own members and dispatches the rest of the task to Clock by the super.reset() call. Note that writing just reset() would not work as it would call the reset() function of AlarmClock itself. Calling reset() from within itself would cause an infinite recursion.

The reason that I have delayed the definition of toString() until this point is that it must be defined by the override keyword for classes. As we will see in the next chapter, every class is automatically inherited from a superclass called Object and Object already defines a toString() member function.

For that reason, the toString() member function for classes must be defined by using the override keyword:

import std.string;

class Clock {
    override string toString() const {
        return format("%02s:%02s:%02s", hour, minute, second);
    }

    // ...
}

class AlarmClock : Clock {
    override string toString() const {
        return format("%s ♫%02s:%02s", super.toString(),
                      alarmHour, alarmMinute);
    }

    // ...
}

Note that AlarmClock is again dispatching some of the task to Clock by the super.toString() call.

Those two overrides of toString() allow converting AlarmClock objects to strings:

void main() {
    auto deskClock = new AlarmClock(10, 15, 0, 6, 45);
    writeln(deskClock);
}

The output:

10:15:00 ♫06:45
Using the subclass in place of the superclass

Since the superclass is more general and the subclass is more specialized, objects of a subclass can be used in places where an object of the superclass type is required. This is called polymorphism.

The concepts of general and specialized types can be seen in statements like "this type is of that type": "alarm clock is a clock", "student is a person", "cat is an animal", etc. Accordingly, an alarm clock can be used where a clock is needed, a student can be used where a person is needed, and a cat can be used where an animal is needed.

When a subclass object is being used as a superclass object, it does not lose its own specialized type. This is similar to the examples in real life: Using an alarm clock simply as a clock does not change the fact that it is an alarm clock; it would still behave like an alarm clock.

Let's assume that a function takes a Clock object as parameter, which it resets at some point during its execution:

void use(Clock clock) {
    // ...
    clock.reset();
    // ...
}

Polymorphism makes it possible to send an AlarmClock to such a function:

    auto deskClock = new AlarmClock(10, 15, 0, 6, 45);
    writeln("Before: ", deskClock);
    use(deskClock);
    writeln("After : ", deskClock);

This is in accordance with the relationship "alarm clock is a clock." As a result, the members of the deskClock object get reset:

Before: 10:15:00 ♫06:45
After : 00:00:00 ♫00:00

The important observation here is that not only the members of Clock but also the members of AlarmClock have been reset.

Although use() calls reset() on a Clock object, since the actual object is an AlarmClock, the function that gets called is AlarmClock.reset. According to its definition above, AlarmClock.reset resets the members of both Clock and AlarmClock.

In other words, although use() uses the object as a Clock, the actual object may be an inherited type that behaves in its own special way.

Let's add another class to the Clock hierarchy. The reset() function of this type sets its members to random values:

import std.random;

class BrokenClock : Clock {
    this() {
        super(0, 0, 0);
    }

    override void reset() {
        hour = uniform(0, 24);
        minute = uniform(0, 60);
        second = uniform(0, 60);
    }
}

When an object of BrokenClock is sent to use(), then the special reset() function of BrokenClock would be called. Again, although it is passed as a Clock, the actual object is still a BrokenClock:

    auto shelfClock = new BrokenClock;
    use(shelfClock);
    writeln(shelfClock);

The output shows random time values as a result of resetting a BrokenClock:

22:46:37
Inheritance is transitive

Polymorphism is not just limited to two classes. Subclasses of subclasses can also be used in place of any superclass in the hierarchy.

Let's consider the MusicalInstrument hierarchy:

class MusicalInstrument {
    // ...
}

class StringInstrument : MusicalInstrument {
    // ...
}

class Violin : StringInstrument {
    // ...
}

The inheritances above builds the following relationships: "string instrument is a musical instrument" and "violin is a string instrument." Therefore, it is also true that "violin is a musical instrument." Consequently, a Violin object can be used in place of a MusicalInstrument.

Assuming that all of the supporting code below has also been defined:

void playInTune(MusicalInstrument instrument,
                MusicalPiece piece) {
    instrument.tune();
    instrument.play(piece);
}

// ...

auto myViolin = new Violin;
playInTune(myViolin, improvisation);

Although playInTune() expects a MusicalInstrument, it is being called with a Violin due to the relationship "violin is a musical instrument."

Inheritance can be as deep as needed.

Abstract member functions and abstract classes

Sometimes there are member functions that are natural to appear in a class interface even though that class cannot provide its definition. When there is no concrete definition of a member function, that function is an abstract member function. A class that has at least one abstract member function is an abstract class.

For example, the ChessPiece superclass in a hierarchy may have an isValid() member function that determines whether a given move is valid for that chess piece. Since validity of a move depends on the actual type of the chess piece, the ChessPiece general class cannot make this decision itself. The valid moves can only be known by the subclasses like Pawn, King, etc.

The abstract keyword specifies that the inherited class must implement such a method itself:

class ChessPiece {
    abstract bool isValid(in Square from, in Square to);
}

It is not possible to construct objects of abstract classes:

    auto piece = new ChessPiece;    // ← compilation ERROR

The subclass would have to override and implement all the abstract functions in order to make the class non-abstract and therefore constructible:

class Pawn : ChessPiece {
    override bool isValid(in Square from, in Square to) {
        // ... the implementation of isValid for pawn ...
        return decision;
    }
}

It is now possible to construct objects of Pawn:

    auto piece = new Pawn;             // compiles

Note that an abstract function may have an implementation of its own, but it would still require the subclass to provide its own implementation of such a function. For example, the ChessPiece'es implementation may provide some useful checks of its own:

class ChessPiece {
    abstract bool isValid(in Square from, in Square to) {
        // We require the 'to' position to be different than
        // the 'from' position
        return from != to;
    }
}

class Pawn : ChessPiece {
    override bool isValid(in Square from, in Square to) {
        // First verify if it is a valid move for any ChessPiece
        if (!super.isValid(from, to)) {
            return false;
        }

        // ... then check if it is valid for the Pawn ...

        return decision;
    }
}

The ChessPiece class is still abstract even though isValid() was already implemented, but the Pawn class is non-abstract and can be instantiated.

Example

Let's consider a class hierarchy that represents railway vehicles:

           RailwayVehicle
          /      |       \
  Locomotive   Train   RailwayCar { load()?, unload()? }
                          /   \
               PassengerCar   FreightCar

The functions that RailwayCar will declare as abstract are indicated by question marks.

Since my goal is only to present a class hierarchy and point out some of its design decisions, I will not fully implement these classes. Instead of doing actual work, they will simply print messages.

The most general class of the hierarchy above is RailwayVehicle. In this program, it will only know how to move itself:

class RailwayVehicle {
    void advance(in size_t kilometers) {
        writefln("The vehicle is advancing %s kilometers",
                 kilometers);
    }
}

A class that inherits from RailwayVehicle is Locomotive, which does not have any special members yet:

class Locomotive : RailwayVehicle {
}

We will add a special makeSound() member function to Locomotive later during one of the exercises.

RailwayCar is a RailwayVehicle as well. However, if the hierarchy supports different types of railway cars, then certain behaviors like loading and unloading must be done according to their exact types. For that reason, RailwayCar can only declare these two functions as abstract:

class RailwayCar : RailwayVehicle {
    abstract void load();
    abstract void unload();
}

Loading and unloading a passenger car is as simple as opening the doors of the car, while loading and unloading a freight car may involve porters and winches. The following subclasses provide definitions for the abstract functions of RailwayCar:

class PassengerCar : RailwayCar {
    override void load() {
        writeln("The passengers are getting on");
    }

    override void unload() {
        writeln("The passengers are getting off");
    }
}

class FreightCar : RailwayCar {
    override void load() {
        writeln("The crates are being loaded");
    }

    override void unload() {
        writeln("The crates are being unloaded");
    }
}

Being an abstract class does not preclude the use of RailwayCar in the program. Objects of RailwayCar can not be constructed but RailwayCar can be used as an interface. As the subclasses define the two relationships "passenger car is a railway car" and "freight car is a railway car", the objects of PassengerCar and FreightCar can be used in places of RailwayCar. This will be seen in the Train class below.

The class that represents a train can consist of a locomotive and an array of railwaycars:

class Train : RailwayVehicle {
    Locomotive locomotive;
    RailwayCar[] cars;

    // ...
}

I would like to repeat an important point: Although both Locomotive and RailwayCar inherit from RailwayVehicle, it would not be correct to inherit Train from either of them. Inheritance models the "is a" relationship and a train is neither a locomotive nor a passenger car. A train consists of them.

If we require that every train must have a locomotive, the Train constructor must ensure that it takes a valid Locomotive object. Similarly, if the railway cars are optional, they can be added by a member function:

import std.exception;
// ...

class Train : RailwayVehicle {
    // ...

    this(Locomotive locomotive) {
        enforce(locomotive !is null,
                "Locomotive cannot be null");
        this.locomotive = locomotive;
    }

    void addCar(RailwayCar[] cars...) {
        this.cars ~= cars;
    }

    // ...
}

Note that addCar() can validate the RailwayCar objects as well. I am ignoring that validation here.

We can imagine that the departures and arrivals of trains should also be supported:

class Train : RailwayVehicle {
    // ...

    void departStation(string station) {
        foreach (car; cars) {
            car.load();
        }

        writefln("Departing from %s station", station);
    }

    void arriveStation(string station) {
        writefln("Arriving at %s station", station);

        foreach (car; cars) {
            car.unload();
        }
    }
}

The following main() is making use of the RailwayVehicle hierarchy:

import std.stdio;

// ...

void main() {
    auto locomotive = new Locomotive;
    auto train = new Train(locomotive);

    train.addCar(new PassengerCar, new FreightCar);

    train.departStation("Ankara");
    train.advance(500);
    train.arriveStation("Haydarpaşa");
}

The Train class is being used by functions that are provided by two separate interfaces:

  1. When the advance() function is called, the Train object is being used as a RailwayVehicle because that function is declared by RailwayVehicle.
  2. When the departStation() and arriveStation() functions are called, train is being used as a Train because those functions are declared by Train.

The arrows indicate that load() and unload() functions work according to the actual type of RailwayCar:

The passengers are getting on     
The crates are being loaded       
Departing from Ankara station
The vehicle is advancing 500 kilometers
Arriving at Haydarpaşa station
The passengers are getting off    
The crates are being unloaded     
Summary
Exercises
  1. Let's modify RailwayVehicle. In addition to reporting the distance that it advances, let's have it also make sounds. To keep the output short, let's print the sounds per 100 kilometers:
    class RailwayVehicle {
        void advance(in size_t kilometers) {
            writefln("The vehicle is advancing %s kilometers",
                     kilometers);
    
            foreach (i; 0 .. kilometers / 100) {
                writefln("  %s", makeSound());
            }
        }
    
        // ...
    }
    

    However, makeSound() cannot be defined by RailwayVehicle because vehicles may have different sounds:

    • "choo choo" for Locomotive
    • "clack clack" for RailwayCar

    Note: Leave Train.makeSound to the next exercise.

    Because it must be overridden, makeSound() must be declared as abstract by the superclass:

    class RailwayVehicle {
        // ...
    
        abstract string makeSound();
    }
    

    Implement makeSound() for the subclasses and try the code with the following main():

    void main() {
        auto railwayCar1 = new PassengerCar;
        railwayCar1.advance(100);
    
        auto railwayCar2 = new FreightCar;
        railwayCar2.advance(200);
    
        auto locomotive = new Locomotive;
        locomotive.advance(300);
    }
    

    Make the program produce the following output:

    The vehicle is advancing 100 kilometers
      clack clack
    The vehicle is advancing 200 kilometers
      clack clack
      clack clack
    The vehicle is advancing 300 kilometers
      choo choo
      choo choo
      choo choo
    

    Note that there is no requirement that the sounds of PassengerCar and FreightCar be different. They can share the same implemention from RailwayCar.

  2. Think about how makeSound() can be implemented for Train. One idea is that Train.makeSound may return a string that consists of the sounds of the members of Train.