Potrei leggere male la tua domanda, ma ci provo perché sospetto che sia una domanda che ho avuto anche a un certo punto.
Potrebbe sembrare che Java non abbia un'ereditarietà di implementazione multipla perché è difficile da implementare, mentre C ++ lo ha perché i suoi implementatori hanno trovato un modo per implementarlo. Questo non è il caso. Come altri hanno già detto, è stata deliberata una decisione progettuale di evitare una categoria di problemi di programmazione, non qualcosa che permettesse una più facile implementazione di Java.
Il fatto stesso che Java abbia ereditarietà multipla dalle interfacce significa che i suoi implementatori hanno fatto il duro lavoro di implementare l'ereditarietà multipla in ogni caso - il fatto che le interfacce siano astratte è per lo più irrilevante.
In un'implementazione che utilizza vtables, per implementare il polimorfismo sotto ereditarietà singola, è necessario un vtable del tipo runtime dell'istanza, perferenzialmente in una posizione predeterminata. Per implementare il polimorfismo sotto ereditarietà multipla, sono necessari più vtables.
Per semplicità, diciamo che il layout di memoria di ogni tipo inizia con il suo vtable (non è richiesto che sia così, ma renderà la mia descrizione più semplice).
Type A [vtableA, mem1, mem2, ...]
Type B [vtableB, mem3, mem4, ...]
Type C : A, B [vtableA, mem1, mem2, ..., vtableB, mem3, mem4,...]
C* from C ^
Nell'ambito dell'ereditarietà singola, devi solo passare il riferimento all'oggetto e puoi sempre trovare il vtable del suo tipo di runtime.
Type C : A, B [vtableA, mem1, mem2, ..., vtableB, mem3, mem4,...]
A* from C ^
In ereditarietà multipla, per utilizzare la sostituzione, devi passare un riferimento tale che, se interpretato come riferimento a un'istanza di un supertipo, funzionerà comunque. Ciò significa che il riferimento dovrebbe puntare a un altro vtable nel layout di memoria dell'intera istanza. Questo è fatto semplicemente aggiungendo un offset, fino a quando il nuovo riferimento punta al nuovo vtable. Questo offset è noto al momento della compilazione ed è probabilmente la dimensione totale dei dati precedenti nel layout.
Type C : A, B [vtableA, mem1, mem2, ..., vtableB, mem3, mem4,...]
B* from C ^
<----memory size of A---->
Si noti che negli schemi ho usato "type", non "class". Se si presume che la lingua non consenta di dichiarare i propri membri dei dati, tutto quanto sopra rimane valido. L'unica differenza è che Type B
ora sarà composto solo da un vtable (perché il linguaggio non consentirà mem3
, mem4
, ecc.). Ma il meccanismo è lo stesso.