Se não houver despacho dinâmico (polimorfismo), "métodos" são apenas funções açucaradas, talvez com um parâmetro adicional implícito. Consequentemente, instâncias de classes sem comportamento polimórfico são essencialmente Cs struct
para fins de geração de código.
Para despacho dinâmico clássico em um sistema de tipo estático, existe basicamente uma estratégia predominante: vtables. Cada instância obtém um ponteiro adicional que se refere a (uma representação limitada) de seu tipo, o mais importante é a vtable: Uma matriz de ponteiros de função, um por método. Como o conjunto completo de métodos para cada tipo (na cadeia de herança) é conhecido em tempo de compilação, é possível atribuir índices consecutivos (0..N para métodos N) aos métodos e invocar os métodos procurando o ponteiro de função em a vtable usando esse índice (passando novamente a referência da instância como parâmetro adicional).
Para linguagens baseadas em classes mais dinâmicas, normalmente as próprias classes são objetos de primeira classe e cada objeto tem uma referência ao seu objeto de classe. O objeto de classe, por sua vez, possui os métodos de alguma maneira dependente da linguagem (no Ruby, os métodos são uma parte essencial do modelo de objeto, no Python são apenas objetos funcionais com pequenos invólucros ao seu redor). As classes normalmente armazenam referências também às suas superclasses e delegam a pesquisa de métodos herdados nessas classes para ajudar na metaprogramação que adiciona e altera métodos.
Existem muitos outros sistemas que não são baseados em classes, mas diferem significativamente; portanto, selecionarei apenas uma alternativa interessante de design: quando você pode adicionar novos (conjuntos de) métodos a todos os tipos à vontade em qualquer lugar do programa ( por exemplo, classes de tipo em Haskell e características em Rust), o conjunto completo de métodos não é conhecido durante a compilação. Para resolver isso, cria-se uma vtable por característica e as transmite quando a implementação da característica é necessária. Ou seja, código como este:
void needs_a_trait(SomeTrait &x) { x.method2(1); }
ConcreteType x = ...;
needs_a_trait(x);
é compilado para isso:
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);
Isso também significa que as informações da tabela não estão incorporadas no objeto. Se você deseja referências a uma "instância de uma característica" que se comportará corretamente quando, por exemplo, armazenada em estruturas de dados que contêm muitos tipos diferentes, é possível criar um ponteiro de gordura (instance_pointer, trait_vtable)
. Esta é realmente uma generalização da estratégia acima.