Nameet Rajore

Explaining the Diamond Problem in Inheritance

I was reading up about something and I realised I don't really understand Inheritance and whats so wrong with multiple inheritance. Why has Java not implemented it? I mean I have a computer science degree, so I know what it is, but if someone asked me to explain why, i would fumble. So I decided to burn some trees and ask chatgpt.

What is inheritance?

Inheritance is a mechanism where the child class acquires properties and methods of its parent class.

For example,

#include <iostream>
using namespace std;

// Base class
class Animal {
public:
    void eat() {
        cout << "This animal eats food." << endl;
    }
};

// Derived class
class Dog : public Animal {
public:
    void bark() {
        cout << "The dog barks." << endl;
    }
};

int main() {
    Dog d;
    d.eat();   // Inherited from Animal
    d.bark();  // Defined in Dog
}

The above example is a classic example of inheritance in cpp. But when I saw this, I realised I didn't actually understand what exactly is meant when we say "the child class acquires properties and methods of its parent class", does it get copied? does it get referenced? So I dived a little deeper and understood Inheritance can be understood differently based on which language you're referencing.

Let's take a different example in C++:

class Animal {
public:
    int age;
};

class Dog : public Animal {
public:
    int weight;
};

int main() {
    Dog d;
    Animal* a = &d;
}

Here, Dog d in memory would look something like this:

Address: 0x1000   [ age     ]   ← Animal base subobject
Address: 0x1004   [ weight  ]   ← Dog part

This means that when we create an object of a child class, the first few bytes are occupied by the copied methods and properties of its parent class.

This clears up two very important things,

  1. The child class acquires properties by copying the parent's sub-object in child objects memory
  2. This ordering of memory makes the statement Animal* a = &d; very easy to compute as the offset in memory is already 0.

Now let's understand what multiple inheritance is. It's when a class inherits two separate classes, which are children of the same parent.

        A
       / \
      B   C
       \ /
        D
class A {
    int x;
};

class B : public A { };

class C : public A { };

class D : public B, public C { };
D:

+----------------+
| B subobject    |
|  +------------+|
|  | A::x       ||
|  +------------+|
+----------------+
| C subobject    |
|  +------------+|
|  | A::x       ||
|  +------------+|
+----------------+

Can you see why multiple inheritance is problematic now? Every time we create an object of D, we get two copies of x! So when we try to reference it like D.x the compiler goes crazy, as it should. It doesn't know which x we want, and neither do we.

This problem was solved by introducing virtual inheritance in cpp. In virtual inheritance, instead of making two separate copies of A, we make one copy, while B and C hold pointers to that copy.

D:

+----------------+
| B subobject    |
|  (pointer to A)|
+----------------+
| C subobject    |
|  (pointer to A)|
+----------------+
| A subobject    |   <-- SINGLE shared A
|  A::x          |
+----------------+

Okay. I get it why I didn't get it initially now. I don't blame myself. This shit is complicated.