Wenn es keinen dynamischen Versand (Polymorphismus) gibt, sind "Methoden" nur zuckerhaltige Funktionen, möglicherweise mit einem impliziten zusätzlichen Parameter. Dementsprechend sind Instanzen von Klassen ohne polymorphes Verhalten im Wesentlichen struct
Cs zum Zweck der Codegenerierung.
Für den klassischen dynamischen Versand in einem statischen Typsystem gibt es grundsätzlich eine vorherrschende Strategie: vtables. Jede Instanz erhält einen zusätzlichen Zeiger, der auf ihren Typ verweist (eine begrenzte Darstellung davon), vor allem auf die vtable: Ein Array von Funktionszeigern, einen Zeiger pro Methode. Da der vollständige Satz von Methoden für jeden Typ (in der Vererbungskette) zur Kompilierungszeit bekannt ist, können den Methoden aufeinanderfolgende Indizes (0..N für N Methoden) zugewiesen und die Methoden aufgerufen werden, indem der Funktionszeiger in nachgeschlagen wird Die vtable verwendet diesen Index (wobei die Instanzreferenz erneut als zusätzlicher Parameter übergeben wird).
Bei dynamischeren klassenbasierten Sprachen sind Klassen in der Regel selbst erstklassige Objekte, und jedes Objekt verfügt stattdessen über einen Verweis auf sein Klassenobjekt. Das Klassenobjekt wiederum besitzt die Methoden in gewisser sprachabhängiger Weise (in Ruby sind Methoden ein Kernbestandteil des Objektmodells, in Python sind sie nur Funktionsobjekte mit winzigen Umhüllungen). Die Klassen speichern normalerweise auch Verweise auf ihre Oberklasse (n) und delegieren die Suche nach geerbten Methoden an diese Klassen, um die Metaprogrammierung zu unterstützen, die Methoden hinzufügt und ändert.
Es gibt viele andere Systeme, die nicht auf Klassen basieren, die sich jedoch erheblich unterscheiden. Ich werde daher nur eine interessante Designalternative herausgreifen: Wenn Sie an einer beliebigen Stelle im Programm neue (Gruppen von) Methoden zu allen Typen hinzufügen können ( B. Typklassen in Haskell und Eigenschaften in Rust), ist der vollständige Methodensatz beim Kompilieren nicht bekannt. Um dies zu beheben, erstellt man eine vtable pro Merkmal und leitet sie weiter, wenn die Implementierung des Merkmals erforderlich ist. Das heißt, Code wie folgt:
void needs_a_trait(SomeTrait &x) { x.method2(1); }
ConcreteType x = ...;
needs_a_trait(x);
ist wie folgt zusammengestellt:
functionpointer SomeTrait_ConcreteType_vtable[] = { &method1, &method2, ... };
void needs_a_trait(void *x, functionpointer vtable[]) { vtable[1](x, 1); }
ConcreteType x = ...;
needs_a_trait(x, SomeTrait_ConcreteType_vtable);
Dies bedeutet auch, dass die vtable-Informationen nicht in das Objekt eingebettet sind. Wenn Sie Verweise auf eine "Instanz eines Merkmals" wünschen, die sich korrekt verhält, wenn sie beispielsweise in Datenstrukturen gespeichert werden, die viele verschiedene Typen enthalten, können Sie einen Fettzeiger erstellen (instance_pointer, trait_vtable)
. Dies ist eigentlich eine Verallgemeinerung der obigen Strategie.