Introdução
Há dois termos que são normalmente usados quando se discute polimorfismo em linguagens de programação orientada a objeto: linkagem estática e linkagem dinâmica. Em C/C++, referem-se à quando os tipos envolvidos podem ser resolvidos em tempo de compilação e em tempo de execução respectivamente.
Na linkagem estática, o compilador é feito de forma a poder resolver os tipos envolvidos de forma segura, sem a necessidade de manter qualquer estrutura em tempo de execução. Podemos ver isso quando temos um ponteiro que pode acessar uma função membro, se isso é possível certamente o objeto referenciado pelo ponteiro também pode acessar a mesma função membro, desde que o objeto referenciado seja coerente com o tipo do ponteiro. Portanto podemos determinar em tempo de compilação, qual função membro deve ser executada neste caso. Como exemplo de linkagem estática temos os argumentos padrões, templates (conhecido como polimorfismo paramétrico) e etc.
Já a linkagem dinâmica se faz necessária porque há casos em que não é possível determinar se a conversão de um tipo base em um tipo derivado é “seguro”, em tempo de compilação, pois os compiladores ainda não são capazes de notar a presença de erros de semântica. Pode-se ver claramente a necessidade, de que se mantenha então, estruturas em tempo de execução, que tenham a capacidade de garantir essa “segurança”. O objetivo principal da linkagem dinâmica é suportar uma interface comum, enquanto permite que vários objetos que utilizam essa interface definam suas próprias implementações. Em C/C++ a linkagem dinâmica é consequência direta do uso de funções virtuais e de herança.
Os tipos de polimorfismo?
Existem basicamente dois grandes grupos de polimorfismo e entende-los pode ajudar a diferenciar melhor o que é linkagem dinâmica do que é linkagem estática nos mais diferentes contextos:
- Polimorfismo Puro
- Polimorfismo Ad-Hoc
O polimorfismo puro compreende o conceito de overhiding e o polimorfismo Ad-Hoc compreende o conceito de overloading (ou sobrecarga) e funções virtuais.
Overloading representa a implementação do polimorfismo estático (que faz uso de linkagem estática), que se caracteriza principalmente por operadores ou funções que possuem o mesmo nome (o que não implica numa interface em comum), mas que podem realizar diferentes tarefas para diferentes contextos. Podemos ter aqui várias funções com o mesmo nome, mas com números e tipos de argumento diferentes.. Por exemplo, você pode usar uma função para somar dois inteiros assim como uma para somar dois floats, ambas poderiam ter o mesmo nome, o que mudaria seriam os tipos dos argumentos, mas você ainda teria uma função que pode se comportar de forma diferente de acordo com o contexto.
Enquanto que a concepção de Overhiding é aplicada para o polimorfismo dinâmico (que faz uso da linkagem dinâmica), onde ainda temos funções membro com o mesmo nome, mas o que irá ser feito é definido apenas em tempo de execução de acordo com o tipo de objeto que foi referenciado, sendo portanto uma consequência direta do uso de herança. Aqui temos uma completa substituição de uma função para outra independente dos tipos ou quantidade de argumentos. Como exemplo, poderíamos ter uma classe base que implementa uma função e uma classe derivada que muda o comportamento da função, o que irá acontecer na execução do programa depende apenas do tipo do objeto.
Como C/C++ proporciona a linkagem dinâmica?
A maioria dos compiladores modernos de C/C++, permitem o overhiding de funções através das funções membro virtuais (ou funções virtuais). O modo de proporcionar as funções virtuais é comumente conhecido como método da tabela virtual, e é inclusive usado por outras linguagens tais como D, C#, Visual BASIC e Delphi, embora estas diferentes linguagens usem terminologias diferentes para seus modos de implementar o polimorfismo dinâmico. Geralmente o método da tabela virtual é implementado usando alguma variante da seguinte técnica: Se o objeto instanciado tem uma ou mais funções virtuais, o compilador coloca dentro do objeto um ponteiro oculto ao programador, denominado virtual-pointer, ou apenas v-pointer. Esse v-pointer aponta para uma tabela denominada virtual-table ou v-table. O compilador cria uma v-table para cada classe que faça uso de funções virtuais, sendo que cada função virtual, tem um ponteiro que aponta para sí como elemento da tabela v-table de sua classe. A v-table de uma classe é totalmente independente da quantidade de objetos instanciados desta classe.
Na imagem abaixo temos um exemplo de uma hierarquia de herança fazendo o uso de funções virtuais para obter polimorfismo através de linkagem dinâmica:
Durante a chamada de uma função virtual, o sistema de execução deferência o v-pointer do objeto, tendo então acesso a v-table da classe na memória, então ele escolhe o elemento da v-table correspondente à função membro virtual em questão e o deferência para ir para o endereço de memória onde o código desta função membro virtual se encontra, conseguindo “gerar” o polimorfismo desejado.
Se supormos que os v-pointers são do tipo v_ptr e que as classes chamam o v-pointer de _v_pointer, poderíamos ter uma organização semelhante a descrita abaixo, com as setas representando para quem os ponteiros apontam:
A uso de v-table e v-pointers porém possui um trade-off implícito, pois seu uso trás um custo extra no tocante ao espaço usado na memória, pois é necessário um ponteiro extra, por objeto instanciado que precise de linkagem dinâmica, para representar o v-pointer e um elemento extra na v-table de cada classe, para cada função virtual desta. Além de, no mínimo, uma deferência extra, em relação as funções não-virtuais, que nada mais fazem do que “pular” para um espaço na memória pré-determinado em tempo de compilação.
Conclusão
Logo, o uso de funções virtuais possui um acesso um pouco mais devagar e gasta mais espaço na memória, embora que as CPU’s modernas e o crescente aumento das memórias tenham tornado esse custo extra relativamente insisgnificante. Para finalizar, podemos ver está quantidade extra de ponteiros e a presença da virtual table na figura abaixo:
Em contrapartida, sabemos que o polimorfismo proporcionado pela linkagem estática não gera overhead algum, o trabalho extra fica totalmente a cargo do compilador. A escolha de qual polimorfismo deve ser implementado depende apenas das necessidades do progamador.
Bibliografia
- [DEITEL, H. M.; DEITEL, P. J.] C++ : How to program; 7a. Edição
- http://www.arnaut.eti.br/op/CPPFL20.htm
- http://pt.wikipedia.org/wiki/C%2B%2B#Polimorfismo
- http://en.wikipedia.org/wiki/Virtual_method_table
0sem comentários ainda