S'il n'y a pas de répartition dynamique (polymorphisme), les "méthodes" ne sont que des fonctions sucrées, peut-être avec un paramètre supplémentaire implicite. Par conséquent, les instances de classes sans comportement polymorphe sont essentiellement des C struct
pour la génération de code.
Pour la répartition dynamique classique dans un système de type statique, il existe essentiellement une stratégie prédominante: les vtables. Chaque instance obtient un pointeur supplémentaire qui fait référence à (une représentation limitée de) son type, le plus important étant la table virtuelle: un tableau de pointeurs de fonction, un par méthode. Étant donné que l'ensemble complet des méthodes pour chaque type (dans la chaîne d'héritage) est connu au moment de la compilation, on peut attribuer des indices consécutifs (0..N pour N méthodes) aux méthodes et appeler les méthodes en recherchant le pointeur de fonction dans la table virtuelle en utilisant cet index (en passant à nouveau la référence d'instance comme paramètre supplémentaire).
Pour les langages basés sur des classes plus dynamiques, les classes elles-mêmes sont généralement des objets de première classe et chaque objet a une référence à son objet de classe. L'objet classe, à son tour, possède les méthodes d'une manière dépendante du langage (dans Ruby, les méthodes sont une partie centrale du modèle objet, en Python, ce ne sont que des objets fonction avec de minuscules enveloppes autour d'eux). Les classes stockent généralement également les références à leurs superclasses et délèguent la recherche des méthodes héritées à ces classes pour faciliter la métaprogrammation qui ajoute et modifie les méthodes.
Il existe de nombreux autres systèmes qui ne sont pas basés sur des classes, mais ils diffèrent considérablement, donc je ne choisirai qu'une alternative de conception intéressante: lorsque vous pouvez ajouter de nouveaux (ensembles de) méthodes à tous les types à volonté n'importe où dans le programme ( ex: classes de types dans Haskell et traits dans Rust), l'ensemble complet des méthodes n'est pas connu lors de la compilation. Pour résoudre ce problème, on crée une table virtuelle par trait et les transmet lorsque l'implémentation du trait est requise. Autrement dit, un code comme celui-ci:
void needs_a_trait(SomeTrait &x) { x.method2(1); }
ConcreteType x = ...;
needs_a_trait(x);
se résume à ceci:
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);
Cela signifie également que les informations vtable ne sont pas intégrées dans l'objet. Si vous voulez des références à une "instance d'un trait" qui se comportera correctement lorsque, par exemple, stockées dans des structures de données qui contiennent de nombreux types différents, vous pouvez créer un gros pointeur (instance_pointer, trait_vtable)
. Il s'agit en fait d'une généralisation de la stratégie ci-dessus.