Se non esiste un dispacciamento dinamico (polimorfismo), i "metodi" sono solo funzioni zuccherine, forse con un parametro implicito aggiuntivo. Di conseguenza, le istanze di classi senza comportamento polimorfico sono essenzialmente C struct
ai fini della generazione del codice.
Per l'invio dinamico classico in un sistema di tipo statico, esiste sostanzialmente una strategia predominante: vtables. Ogni istanza ottiene un puntatore aggiuntivo che fa riferimento a (una rappresentazione limitata di) il suo tipo, soprattutto la vtable: un array di puntatori a funzione, uno per metodo. Poiché l'intero set di metodi per ogni tipo (nella catena di ereditarietà) è noto al momento della compilazione, è possibile assegnare indici consecutivi (0..N per N metodi) ai metodi e richiamare i metodi cercando il puntatore a funzione in la vtable usando questo indice (passando nuovamente il riferimento all'istanza come parametro aggiuntivo).
Per linguaggi più dinamici basati su classi, in genere le classi stesse sono oggetti di prima classe e ogni oggetto ha invece un riferimento al suo oggetto classe. L'oggetto classe, a sua volta, possiede i metodi in qualche modo dipendente dalla lingua (in Ruby, i metodi sono una parte fondamentale del modello a oggetti, in Python sono solo oggetti funzione con piccoli involucri attorno a loro). Le classi in genere memorizzano anche i riferimenti alle loro superclassi e delegano la ricerca di metodi ereditati a tali classi per facilitare la metaprogrammazione che aggiunge e modifica i metodi.
Esistono molti altri sistemi che non si basano sulle classi, ma differiscono in modo significativo, quindi selezionerò solo un'interessante alternativa di progettazione: quando puoi aggiungere nuovi (set di) metodi a tutti i tipi a piacimento nel programma ( ad es. classi di tipi in Haskell e tratti in Rust), non è noto l'intero set di metodi durante la compilazione. Per risolvere questo, si crea una vtable per tratto e li passa in giro quando è richiesta l'implementazione del tratto. Cioè, codice come questo:
void needs_a_trait(SomeTrait &x) { x.method2(1); }
ConcreteType x = ...;
needs_a_trait(x);
è compilato in questo modo:
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);
Questo significa anche che le informazioni sulla vtable non sono incorporate nell'oggetto. Se desideri riferimenti a una "istanza di un tratto" che si comporterà correttamente quando, ad esempio, è archiviato in strutture di dati che contengono molti tipi diversi, puoi creare un puntatore grasso (instance_pointer, trait_vtable)
. Questa è in realtà una generalizzazione della strategia di cui sopra.