Virtuelle Methodentabellen

Werbung
Einführung in die Systemprogrammierung
Virtuelle Methodentabellen
Prof. Dr. Christoph Reichenbach
Fachbereich 12 / Institut für Informatik
9. Juli 2015
Struktur der Objektorientierung
Ein wenig Java:
public class Tier {
int alter;
// Definition der Klasse „Tier“
// Feld (wie in struct)
Tier() {
this.alter = 0;
}
// Konstruktor (erzeugt Objekt)
int getAlter() {
// Methode (ähnlich Funktion)
return this.alter;
}
void tuEtwas() {}
}
Objektorientierung versus C
Grundelemente typischer objektorientierter Sprachen
I
Klasse:
I
I
I
I
Konstruktor:
I
I
I
I
Ähnlich struct: Beinhaltet Felder
Beinhaltet außerdem Konstruktoren und Methoden
Kann Felder und Methoden erben
Ähnlich wie malloc() plus Initialisierung
Schreibt Zeiger auf Typdeskriptor in Speicherobjekt
Erzeugt Objekt/Instanz der Klasse
Methode:
I
I
I
Ähnlich Funktion
Hat zusätzlichen, meist impliziten Parameter
(Java: this, Python: self)
this zeigt immer auf Instanz der umgebenden Klasse
oder einer Subklasse
Klassen und Vererbung
public class Tier {
int alter;
Tier() {
this.alter = 0;
}
int getAlter() {
return this.alter;
}
void tuEtwas() {}
}
static void machwas(Tier t) {
// Was passiert hier?
t.tuEtwas();
}
machwas(new Katze());
machwas(new Kuh());
public class Katze extends Tier {
void tuEtwas() {
System.out.println("Miau");
}
}
public class Kuh extends Tier {
void tuEtwas() {
System.out.println("<wiederkau>");
}
}
Wie kann machwas wissen, welche Methode es aufruft?
Implementierung von „Tier“
Java
public class Tier {
int alter;
Tier() {
this.alter = 0;
}
int getAlter() {
return this.alter;
}
void tuEtwas() {}
}
Äquivalentes C
typedef struct {
typdeskriptor td;
int alter;
} Tier;
typdeskriptor TD_TIER = ...;
Tier *new__Tier() {
Tier *t = malloc(sizeof(Tier));
t->td = TD_TIER;
t->alter = 0;
return t;
}
int Tier__getAlter(Tier *this) {
return this->alter;
}
void Tier__tuEtwas(Tier *this) {}
Implementierung von „Katze“
Java
Äquivalentes C
public class Katze
typedef Tier Katze;
extends Tier {
// gleiche Struktur
// int alter; (geerbt) typdeskriptor TD_KATZE = ...;
Katze() {}
void tuEtwas() {
System.out.println(
"Miau");
}
}
Katze *new__Katze() {
Tier *t = malloc(sizeof(Katze));
t->td = TD_KATZE;
t->alter = 0;
return t;
}
void Katze__tuEtwas(Katze *this) {
printf("Miau\n");
}
Methodentabelle
Java
Typ
Konstruktor
getAlter
tuEtwas
Tier (C)
Tier
new__Tier
Tier__getAlter
Tier__tuEtwas
Katze (C)
Katze
new__Katze
Tier__getAlter
Katze__tuEtwas
Kuh (C)
Kuh
new__Kuh
Tier__getAlter
Kuh__tuEtwas
I
tuEtwas hat eine eigene Implementierung für jede Klasse
I
getAlter ist vererbt: Jede Klasse benutzt die gleiche
Implementierung
Lösung mit Fallunterscheidung
Java
class Tier ...
class Katze ...
class Kuh ...
static void
machwas(Tier t) {
t.tuEtwas();
}
I
Äquivalent in C
void machwas(Tier *t) {
if (t->td == TD_KATZE)
Katze__tuEtwas((Katze *) t);
else if (t->td == TD_KUH)
Kuh__tuEtwas((Kuh *) t);
else
Tier__tuEtwas(t);
}
Im Prinzip korrekt, aber:
I
I
I
Bei vielen Subklassen große Mengen Code
Sicht auf ganzes Programm nötig
Jede neue Subklasse erzwingt globale Neuübersetzung
Diese Lösung wird normalerweise nicht verwendet
Alternative Lösung: Kreative Nutzung des
Typdeskriptors
I
I
Wir haben bisher nicht diskutiert, wie der Typdeskriptor
aussieht
Eine Möglichkeit: Struktur mit expliziter Methodentabelle
0
1
2
I
I
Typbeschreibung etc.
getAlter
tuEtwas
Eintrag 0 speichert Typinformationen
(zur Laufzeit-Typprüfung, Konvertierung, Reflektion etc.)
(hat meist andere Struktur als folgende Einträge)
Einträge 1 und folgende speichern Funktionszeiger
Virtuelle Methodentabelle
Tier k0 =
new Kuh();
Tier k1 =
new Katze();
Tier k2 =
new Katze();
td
alter: 0
0
1
2
Kuh
Tier__getAlter
Kuh__tuEtwas
0
1
2
Katze
Tier__getAlter
Katze__tuEtwas
td
alter: 0
td
alter: 0
Wichtige Rolle des Typdeskriptors in OO-Sprachen ist
Methodentabelle
Virtuelle Methodentabellen in C
Wir verschieben den Typdeskriptor in „virtuelle Methodentabelle“:
typedef struct {
Tier_vtbl *vtbl;
int alter;
} Tier;
typedef Tier Katze;
Katze *new__Katze() {
Katze *katze = malloc(
sizeof(Katze));
katze->vtbl = &vtbl_KATZE;
katze->alter = 0;
return katze;
}
void machwas(Tier *t) {
t->vtbl->tuEtwas(t);
}
typedef struct {
typdeskriptor td;
int (*getAlter)(Tier *);
void (*tuEtwas)(Tier *);
} Tier_vtbl;
const Tier_vtbl vtbl_TIER = {
.td = TD_TIER,
.getAlter = Tier__getAlter,
.tuEtwas = Tier__tuEtwas };
const Katze_vtbl vtbl_KATZE = {
.td = TD_KATZE,
.getAlter = Tier__getAlter,
.tuEtwas = Katze__tuEtwas };
Subtypen und Methodentabellen
vtbl
alter: 0
fell: 42
0
1
2
3
Katze
Katze__getAlter
Tier__getAlter
Katze__tuEtwas
Katze__jageMaus
Virtuelle Methodentabellen und Trennung von Objekt und
Typdeskriptor erlauben Subtypen:
I
I
I
Methoden zu überschreiben
Neue Methoden einfach hinzuzufügen (einfache
Vererbung)
Neue Felder hinzuzufügen
Zusammenfassung: Subklassen und dynamische
Bindung
I
I
I
I
I
Subklassen können Methoden ihrer Elternklassen
überschreiben
Methoden werden in virtueller Methodentabelle
gespeichert
Jede Klasse hat eigene virtuelle Methodentabelle
Jedes Objekt hat Zeiger auf Methodentabelle seiner Klasse
Subklassen dürfen:
I
I
I
Methoden ändern: Anderer Eintrag in Methodentabelle
Methoden hinzufügen: Neuer Eintrag am Ende der
Methodentabelle
Felder hinzufügen: Neuer Eintrag am Ende der
Typstruktur
Herunterladen