Multiple Inheritance

Dear Computer

Chapter 5: Objects

Multiple Inheritance

Imagine we have a ClickableWidget that is the superclass of all clickable widgets in a graphical user interface. It has state and behaviors for responding to mouse clicks, and its subclasses include Button and Checkbox.

Also imagine we have an AnimatedWidget that is the superclass of all animated widgets. It has state and behaviors for changing a widget's appearance over time, and its subclasses include Marquee and Slideshow.

One day a client asks us for a button that counts down to 0 and then explodes if the user hasn't clicked on it. No problem, we think. We'll just make ExplodingButton, which will be a subclass of both ClickableWidget and AnimatedWidget. But there is a problem. Very few languages allow us to have multiple superclasses.

The primary issue with multiple superclasses is that they introduce ambiguity. If both ClickableWidget and AnimatedWidget have the behavior update, which method should be dispatched for the call boomButton.update()? If both have the state parent, which should be assigned in the statement boomButton.parent = window? Which superclass should be constructed first? Which should be deconstructed first?

These ambiguities are not insurmountable. C++ allows multiple inheritance but forces us to resolve the ambiguities manually. If we call an ambiguous behavior, the compiler will error. We must apply one or both of these solutions: override the colliding methods in the subclass or explicitly qualify the supercalls with the desired superclass. Class C has an ambiguous debug method in this program:

C++
#include <iostream>
using namespace std;

class A {
  public:
    void debug() { cout << "A" << endl; }
};

class B {
  public:
    void debug() { cout << "B" << endl; }
};

class C : public A, public B {
  public:
    // void debug() {
    //   A::debug();
    //   B::debug();
    // }
};

int main() {
  C c;
  // c.debug();  <- ambiguous, uncomment C::debug to resolve
  c.A::debug();  // prints A
  c.B::debug();  // prints B
  return 0;
}

Likewise, if we reference ambiguous state, the compiler will error. These references too must be qualified. Class C has an ambiguous instance variable x in this program:

C++
class A {
  public:
    int x;
};

class B {
  public:
    int x;
};

class C : public A, public B {
  public:
    void set_x(int new_x) {
      // x = new_x;  <- ambiguous and illegal
      A::x = new_x;
      B::x = new_x;
    }
};

int main() {
  C c;
  // c.x = 5;   <- ambiguous and illegal
  c.A::x = 5;
  c.B::x = 5;
  c.set_x(5);
  return 0;
}

Java originally had no support for inheriting state or behaviors from multiple superclasses. But this prohibition was relaxed in Java 8 with default methods. A default method is a method definition provided by an interface. A class that implements multiple interfaces must override any colliding default methods, as class C does:

Java
public class C implements A, B {
  public static void main(String[] args) {
    C c = new C();
    c.debug();
  }

  public void debug() {
    // Without this override, the code doesn't compile.
    A.super.debug();
    B.super.debug();
  }
}

interface A {
  default void debug() {
    System.out.println("A");
  }
}

interface B {
  default void debug() {
    System.out.println("B");
  }
}

The Java designers currently allow only multiple interfaces to be inherited. This restriction means that behaviors can be brought in from multiple sources, but not state. Extending multiple classes would bring in state, but this remains prohibited.

Resolving ambiguities may satisfy the compiler, but there's a deeper semantic danger that comes with multiple inheritance. Consider this diagram of the inheritance hierarchy introduced earlier:

Suppose that both ClickableWidget and AnimatedWidget are themselves subclasses of Widget—as seems sensible. The hierarchy then looks like this:

There is something wrong. ClickableWidget and AnimatedWidget each maintain their own separate Widget foundation. Those separate foundations should almost certainly be fused together to eliminate redundancy and instead share state and behaviors:

This fusing complicates the construction of ExplodingButton instances. It is ominously called the dreaded diamond. Normally the ExplodingButton constructor would invoke the two superclass constructors, which would kick off two separate chains of superconstruction. But if the ClickableWidget and AnimatedWidget have a common ancestor, we don't want them both to call the Widget constructor. C++ allows us to suppress this separate construction through virtual inheritance, which requires two changes to the code. First, the inheritance clauses of the competing superclasses are marked with the virtual modifier.

C++
class Widget {};
class ClickableWidget : virtual public Widget {};
class AnimatedWidget : virtual public Widget {};

Second, the subclass at the bottom of the diamond, ExplodingButton in this case, must take complete control over the construction of the superclasses to ensure that no constructor is called twice:

C++
class ExplodingButton : public ClickableWidget, public AnimatedWidget {
  // construct the whole hierarchy manually
  ExplodingButton()
    : Widget(),
      ClickableWidget(),
      AnimatedWidget() {
  }
}

C++ demonstrates that multiple inheritance is possible, but it also reveals its complexity. Few language designers have found its benefits to exceed its costs.

← Bootstrapping ClassesMixins →