Se non ci sono invii dinamici (polimorfismo), i "metodi" sono solo funzioni zuccherine, forse con un parametro aggiuntivo implicito. Di conseguenza, le istanze di classi senza comportamento polimorfico sono essenzialmente C struct
s allo scopo di generare codice.
Per la classica spedizione dinamica in un sistema di tipo statico, esiste fondamentalmente una strategia predominante: vtables. Ogni istanza ottiene un puntatore aggiuntivo uno che fa riferimento a (una rappresentazione limitata di) il suo tipo, soprattutto il vtable: un array di puntatori di funzione, uno per metodo. Poiché l'insieme completo di metodi per ogni tipo (nella catena di ereditarietà) è noto al momento della compilazione, è possibile assegnare indici consecutivi (0..N per metodi N) ai metodi e richiamare i metodi cercando il puntatore della funzione in il vtable usando questo indice (di nuovo passando il riferimento all'istanza come parametro aggiuntivo).
Per linguaggi basati su classi più dinamici, in genere le classi stesse sono oggetti di prima classe e ogni oggetto ha invece un riferimento al suo oggetto di 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 wrapper attorno ad essi). Le classi tipicamente memorizzano anche riferimenti alla loro superclasse e delegano la ricerca di metodi ereditati a quelle classi per facilitare la metaprogrammazione che aggiunge e altera i metodi.
Ci sono molti altri sistemi che non si basano sulle classi, ma differiscono in modo significativo, quindi selezionerò solo un'interessante alternativa di design: quando puoi aggiungere nuovi (set di) metodi a tutti i tipi a volontà ovunque il programma (ad esempio, classi di tipi in Haskell e tratti in Rust), l'insieme completo di metodi non è noto durante la compilazione. Per risolvere questo problema, si crea un per tratto e lo si 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 vtable non sono incorporate nell'oggetto. Se vuoi fare riferimento a una "istanza di un tratto" che si comporterà correttamente quando, ad esempio, sono memorizzati in strutture di dati che contengono molti tipi diversi, è possibile creare un indicatore di grasso (instance_pointer, trait_vtable)
. Questa è in realtà una generalizzazione della strategia di cui sopra.