Multiple Inheritance
Seminario curato da
Emanuele Debenedetti
Paolo Gasti
Angela Locoro
Perché usare l’ereditarietà multipla
• Consideriamo di voler simulare una rete di computer:
Switch
Librerie di
sistema
Classe
NetworkDevice
Classe
Displayed
Classe
Terminal
Terminals
•L’ereditarietà multipla permette di combinare
concetti indipendenti
•Permette di modellare molti problemi in
maniera più elegante
•I vantaggi risultano ancora più evidenti se si
aumenta il numero di classi di base
Svantaggi di questo approccio

Non è sempre banale comprendere il
comportamento di costruttori e distruttori

Realizzare di una implementazione efficiente è più
complessa rispetto al caso dell’ereditarietà singola

Oggi si tende a enfatizzare la differenza tra
sottotipazione e derivazione dell’implementazione

Spesso si incontra il problema del diamante
Il problema del diamante

Se una classe eredita operazioni con
lo stesso nome da più di un genitore
avviene un conflitto

Ci sono due strategie d’uso comune
per gestire questa situazione:
 gestire direttamente il grafo di
ereditarietà
 trasformarlo in una catena lineare e
gestirlo come ereditarietà singola
Z
Y1
Y2
X
Soluzione Graph – Oriented

La semantica di tali linguaggi modella
direttamente il grafo di ereditarietà

Se un’operazione è definita solo dalla
classe z, è ereditata da y1 e da x
senza errori

Si deve prevedere il caso di
ridefinizione dei metodi doppi da
parte di y2
Z
Y1
Y2
X
Soluzione Lineare
Z


Y2

La gerarchia di derivazione viene
linearizzata
Si elimina l’invocazione multipla di
metodi della soluzione precedente
Svantaggi

Y1

X

Lo sviluppatore non è al corrente della
gerarchia di sottotipazione implicita
La selezione del metodo da utilizzare è a
discrezione del compilatore
Problemi nel collegamento con il genitore
effettivo
La soluzione del C++

Per ogni classe specificata come non virtuale l’oggetto
della classe derivata conterrà un subobject di quel tipo
L
L
A
B
C
class L {....}
class A: public L {....}
class B: public L {....}
class C: public A, public B {....}
Per un metodo f
definito in L
occorrerà definire
esplicitamente a quale
dei due subobject L si
vuol fare riferimento:
es. void C::f()
{A::next = B::next}
si riferisce ad
entrambi
La soluzione del C++

Una classe può avere derivazioni virtuali e
non virtuali
B
X
B
Y
Z
AA
class B {....}
class X: virtual public B {....}
class Y: virtual public B {....}
class Z: public B {....}
class AA: public X, public Y, public Z {....}
Ogni oggetto di classe
AA avrà un’occorrenza
del subobject B per ogni
occorrenza virtuale di B
ed un subobject di B per
ogni singola occorrenza
di B non virtuale
nell’esempio due
occorrenze di B: il B di Z
e il B virtuale condiviso
da X ed Y
Implementazione di un oggetto





Un istanza di un oggetto è rappresentata in modo
simile a un record
Si implementa mediante un’area di memoria contigua
Contiene informazioni sulle variabili disposte in
ordine di dichiarazione
Non necessita di informazioni sui suoi metodi non
virtuali
Contiene un puntatore alla virtual method table
(VMT)
Implementazione dell’ereditarietà singola

Consideriamo gli oggetti istanziati dalle seguenti classi:
class thing
{
int age;
}
class animal : thing
{
string food;
}
class leggedAnimal : animal
{
int noOfLegs;
}
Implementazione dell’ereditarietà multipla

Utilizzando lo schema precedente, i campi degli oggetti verranno disposti in
memoria nel seguente modo:
class animal : thing
{
string food;
}
class leggedThing : thing
{
int noOfLegs;
}
class leggedAnimal : animal, leggedThing
{
}
•Utilizzando slot con indirizzo fisso vengono lasciati gap, sprecando spazio
•Non è possibile decidere a priori quanto spazio lasciare perché per ogni classe
esistono infinite classi derivate
Implementazione dell’ereditarietà multipla

Nello schema implementativo tradizionale leggedAnimal sarà:
(thing *) pLeggedAnimal, pLeggedAnimal
thing
(animal *) pLeggedAnimal
animal
leggedThing
VMT *
Variabili di classe statiche
Virtual Method Table
this
Codice dei metodi
Implementazione classica e il casting


Il casting esplicito costringe a sommare o
sottrarre un delta al puntatore alla classe
Nasce il problema del puntatore a NULL
(thing *) pLeggedAnimal, pLeggedAnimal
thing
(animal *) pLeggedThing
animal
pAnimal = pLeggedAnimal + Delta(thing, animal)
pLeggedAnimal = pAnimal – Delta(thing, animal)
leggedThing
pLeggedAnimal = (pAnimal==0) ? 0 : (pAnimal – Delta(thing, animal))
Implementazione mediante Address Map

È posizionata all’inizio di ogni oggetto

Contiene i riferimenti ai suoi campi sotto forma di
stringa – puntatore

L’aggiunta di nuovi campi è molto semplice

Si può ridurre la dimensione degli indici sostituendoli
con codici di hash
Implementazione mediante Address Map

Per una maggiore efficienza spaziale si può utilizzare una sola copia
dell’address map per ogni tipo di oggetto

Resta ancora il limite della ricerca basata su hashing
Si può allora introdurre un dizionario che metta in relazione una
stringa e un indice intero, ma si rendono più complicati il
decentramento e la compilazione separata

Implementazione mediante Address Map


Ogni oggetto può essere visto da più punti di vista
Mediante più address map è semplice realizzare
un’implementazione efficiente
Twin Object

I twin object sono un modo di esprimere la
multiple inheritance usando la single inheritance

I twin object possono essere usati per
implementare la multiple inheritance in linguaggi
che non la supportano, ad esempio in Java

Quindi la multiple inheritance non aumenta
l’espressività del linguaggio, ma rende la
notazione più compatta e da
un’implementazione più efficiente
Schemi

Multiple Inheritance
P2
P1

Twin objects
P2
P1
C
C


C1
T1
T2
C2
C1 e C2 sono chiamati twins (gemelli) esprimendo il fatto che
sono sempre generati assieme
T1 e T2 sono dei puntatori usati per riferirsi da un twins
all’altro
Twin

Se la nostra classe C ereditasse da n classi base, ci
sarebbero n twins
P1
P2
C1
P3
C3
C2
Variabili e metodi aggiuntivi

Se dobbiamo inserire nella nostra classe variabili o
metodi aggiuntivi (non definiti nelle classi basi)


o creiamo una classe aggiuntiva C in cui li inseriamo
o li mettiamo in uno dei due twin (ad es. in C1, che chiamiamo C)
P2
P1
C
T1
T2
C2
Ereditare da una classe twin
P2
P1
C
G
T1
T2
C2
ERRATO!!
G eredita solo da C, ed è
incompatibile con C2
P2
P1
C
T1
G1
T1
T2
T2
C2
G2
Name clashes ed antenato comune

I twin object non soffrono del problema del namely
clashes


infatti se le classi base hanno campi o metodi con lo stesso
nome, essi saranno ereditati da classi diverse (C1 erediterà
i metodi di P1 e C2 i metodi di P2), quindi non si avrà un
name clash
Un discorso analogo vale quando le classi base
hanno un antenato in comune (non si ha il problema
del diamante)
Efficienza

i twin comunicano tramite composizione, quindi
perdono tempo nel passaggio di messaggi
(meno efficiente della single inheritance)

comunque la multiple inheritance è molto meno
efficiente che la single inheritance

 il costo dei twin object non è il problema
maggiore
Ottimizzare i twin objects

L’implementazione vista precedentemente
può essere ottimizzata:



Raggruppando i twins in un blocco
Mantenendo l’ordine di allocazione per avere un
offset noto a tempo di compilazione.
Memorizzando i metodi in blocchi contigui di
memoria
C1
C
C2
Eliminazione transitività virtuale e devirtualizzazione

Semplificano le gerarchie delle classi
TRANSITIVITÀ
DEVIRTUALIZZAZIONE
ESEMPIO
x
y
z
Y è duplicata:
non può essere
devirtualizzata
Y2 non è duplicata:
è derivalizzabile
la devirtualizzazione
implicherebbe due
sott’oggetti x in z, il che
viola la semantica della
virtual inheritance
Originale
Senza
transitività
Devirtualizzata
Inlining virtual bases

Invece di memorizzare un puntatore ad una virtual base
subobject, questo subobject può essere memorizzato in
un offset fisso nel memory layout della classe derivata
Ridotto il tempo di accesso di
d ad a
Eliminato il VBPTR da b ad a
ed il VPTR separato di a
Come applicare le ottimizzazioni viste

L’eliminazione della transitività deve sempre
essere applicata per prima (abilita la
devirtualizzazione ed elimina i candidati inferiori
all’inlining)

La devirtualizzazione può ridurre drasticamente
il numero di campi generati dal compilatore
Il Bidirectional Object Layout
 Schema per il layout di oggetti che produce oggetti
più piccoli e più rapida invocazione di metodi,
ottimizzando in modo automatico particolari usi della
multiple inheritance
 tipo di gerarchia comune: strato di astrazione che
separa interfaccia ed implementazione
dove Ci sono classi, Ti tipi astratti
le frecce denotano le relazioni:
- è sottotipo di (nell’es. T2 sottotipo di T1)
- implementa (nell’es. C1 implementa T1
- eredita da (nell’es. C2 eredita da C1)
Bidirectional layout di un oggetto
object
Dispatch vectors (d.v.)
Dispatch
header
Separazione tra campi ed
informazioni di dispatch
(d.h.)
fields
Class dispatch vector
Dispatch header: array di puntatori a vari dispatch vectors dei metodi
Class vector: è l’ultimo dei dispatch vectors. Contiene tutti i metodi
dell’oggetto
Fields: variabili di istanza di un oggetto.
Bidirectional layout di un oggetto
Compatibilità tra layout di
una classe e della sua
superclasse

In generale un layout (oggetto, dispatch header, dispatch
vector) è compatibile a quello della corrispondente
superclasse se quest’ultimo è incorporato nel layout della
sottoclasse
Un esempio di implementazione

Le classi CA e CB implementano
rispettivamente i tipi A e B

i layout dei due oggetti mostrano i rispettivi
dispatch headers e i dispatch vectors

Un solo dispatch vector, quello della classe

Indici degli n metodi in un d.v. 0...n-1

indici dei dispatch vector di una classe
-m...n-1
Il layout di un type header
Regola e notazioni per un
semplice type header
layout



INEFFICIENTE
un type header è compatibile col dispatch header di ciascuno dei suoi
supertipi
per ogni Ti i metodi inclusi nel dispatch vector sono quelli per cui non c’è
un puntatore nella parte relativa ai suoi supertipi T1....Ti-1
Il tipo T1 è chiamato il supertipo primario I tipi T2...Tn sono supertipi
secondari
Regole di merging per i type header
Regola 1: Se T ha un supertipo S il cui
header è della stessa dimensione ed
è presente in L sostituisco T ad S se
m (dispatch vector di T senza metodi
di S) non è in conflitto con S
Regola 2: metto S alla fine di T
Nessun conflitto con la parte w di T.

T e suoi supertipi (sottotipi di S)
devono essere su una catena di
supertipo primario

Le diramazioni nella struttura dei
supertipi di T, sono incluse nella parte
w di T
La funzione di merge applicando le 2 regole
Regola M1: un solo type header
Regola M2: come le 2 regole
precedenti, se non ci sono
diramazioni lungo il path da Tn ad S
se ci sono S dev’essere supertipo
primario di Tn
Regola M3: Se nessuna delle due si
può applicare si aggiunge Tn
all’inizio del merge precedente
Regole di merging per i class header
La classe C non eredita da una superclasse


I metodi di C privati sono aggiunti prima all’ultimo
dispatch vector di T. I metodi di C hanno indici
negativi
T ha una gerarchia di tipi singola quindi il class
header di C avrà una sola entry per oggetto
La classe C: implementa T ed eredita da Cs
 L’header di Cs deve esporre un header per S
compatibile con T
 L’header di C è formato estendendo il dispatch vector
di classe di Cs all’indietro ed inglobando l’header di S
 Entrambe le operazioni possono interessare lo
stesso dispatch vector se non compatibili con
l’estensione di S
Regole di merging per i class header
Classe C che implementa T ed eredita da Cs

L’header di C è formato concatenando
l’header di T e Cs, senza merging
Possibili varianti
 C non implementa T
la parte del layout relativa è empty

T è supertipo di Cs
viene aggiunta solo la parte dei metodi di C al
dispatch vector della classe Cs
Confronto fra layout
Concludendo

Oggi disponiamo di metodi efficienti per
implementare l’ereditarietà multipla

Nonostante ciò i linguaggi di programmazione
moderni la implementano solo a livello di interfacce

Questa scelta è dovuta ad una complessità
intrinseca della semantica della ereditarietà multipla

Del resto in alcuni casi l’utilizzo dell’ereditarietà
multipla permette una modellazione più elegante
Scarica

ppt - DISI