runtime polymorphism c
Um estudo detalhado do polimorfismo de tempo de execução em C ++.
O polimorfismo de tempo de execução também é conhecido como polimorfismo dinâmico ou ligação tardia. No polimorfismo de tempo de execução, a chamada de função é resolvida em tempo de execução.
Em contraste, para compilar o tempo de compilação ou polimorfismo estático, o compilador deduz o objeto em tempo de execução e, a seguir, decide qual chamada de função vincular ao objeto. Em C ++, o polimorfismo do tempo de execução é implementado usando a substituição de método.
Neste tutorial, exploraremos tudo sobre o polimorfismo de tempo de execução em detalhes.
=> Verifique TODOS os tutoriais em C ++ aqui.
O que você aprenderá:
- Substituição de função
- Função Virtual
- Trabalho de mesa virtual e _vptr
- Funções virtuais puras e classe abstrata
- Destruidores Virtuais
- Conclusão
- Leitura recomendada
Substituição de função
A substituição de função é o mecanismo pelo qual uma função definida na classe base é novamente definida na classe derivada. Nesse caso, dizemos que a função é substituída na classe derivada.
Devemos lembrar que a substituição de função não pode ser feita dentro de uma classe. A função é substituída apenas na classe derivada. Portanto, a herança deve estar presente para a substituição de função.
A segunda coisa é que a função de uma classe base que estamos substituindo deve ter a mesma assinatura ou protótipo, ou seja, deve ter o mesmo nome, mesmo tipo de retorno e mesma lista de argumentos.
Vejamos um exemplo que demonstra a substituição de métodos.
#include using namespace std; class Base { public: void show_val() { cout << 'Class::Base'< Resultado:
Classe :: Base
Classe :: Derivado
No programa acima, temos uma classe base e uma classe derivada. Na classe base, temos uma função show_val que é substituída na classe derivada. Na função principal, criamos um objeto para cada uma das classes Base e Derivada e chamamos a função show_val com cada objeto. Ele produz a saída desejada.
A vinculação de funções acima usando objetos de cada classe é um exemplo de vinculação estática.
Agora vamos ver o que acontece quando usamos o ponteiro da classe base e atribuímos objetos da classe derivada como seu conteúdo.
O programa de exemplo é mostrado abaixo:
#include using namespace std; class Base { public: void show_val() { cout << 'Class::Base'; } }; class Derived:public Base { public: void show_val() //overridden function { cout <<'Class::Derived'; } }; int main() { Base* b; //Base class pointer Derived d; //Derived class object b = &d; b->show_val(); //Early Binding }
Resultado:
Classe :: Base
Agora vemos que a saída é “Class :: Base”. Portanto, independentemente do tipo de objeto que o ponteiro de base está segurando, o programa produz o conteúdo da função da classe cujo tipo de ponteiro de base é. Neste caso, também é realizada a ligação estática.
Para fazer a saída do ponteiro base, conteúdo correto e vinculação adequada, vamos para a vinculação dinâmica de funções. Isso é obtido usando o mecanismo de funções virtuais que é explicado na próxima seção.
Função Virtual
Para que a função sobrescrita seja vinculada dinamicamente ao corpo da função, tornamos a função da classe base virtual usando a palavra-chave “virtual”. Esta função virtual é uma função que é substituída na classe derivada e o compilador executa a vinculação tardia ou dinâmica para esta função.
Agora, vamos modificar o programa acima para incluir a palavra-chave virtual da seguinte maneira:
#include using namespace std;. class Base { public: virtual void show_val() { cout << 'Class::Base'; } }; class Derived:public Base { public: void show_val() { cout <<'Class::Derived'; } }; int main() { Base* b; //Base class pointer Derived d; //Derived class object b = &d; b->show_val(); //late Binding }
Resultado:
Classe :: Derivado
Portanto, na definição de classe de Base acima, tornamos a função show_val “virtual”. Como a função da classe base se torna virtual, quando atribuímos o objeto da classe derivada ao ponteiro da classe base e chamamos a função show_val, a vinculação ocorre em tempo de execução.
Portanto, como o ponteiro da classe base contém o objeto da classe derivada, o corpo da função show_val na classe derivada é vinculado à função show_val e, portanto, à saída.
Em C ++, a função substituída na classe derivada também pode ser privada. O compilador apenas verifica o tipo do objeto em tempo de compilação e vincula a função em tempo de execução, portanto, não faz nenhuma diferença, mesmo se a função for pública ou privada.
Observe que se uma função for declarada virtual na classe base, ela será virtual em todas as classes derivadas.
Mas até agora, não discutimos como exatamente as funções virtuais desempenham um papel na identificação da função correta a ser vinculada ou, em outras palavras, como a vinculação tardia realmente acontece.
A função virtual é ligada ao corpo da função com precisão em tempo de execução, usando o conceito de mesa virtual (VTABLE) e um ponteiro oculto chamado _vptr.
Ambos os conceitos são implementação interna e não podem ser usados diretamente pelo programa.
Trabalho de mesa virtual e _vptr
Primeiro, vamos entender o que é uma mesa virtual (VTABLE).
O compilador em tempo de compilação configura um VTABLE para cada classe com funções virtuais, bem como as classes que são derivadas de classes com funções virtuais.
Uma VTABLE contém entradas que são ponteiros de função para funções virtuais que podem ser chamadas pelos objetos da classe. Existe uma entrada de ponteiro de função para cada função virtual.
No caso de funções virtuais puras, essa entrada é NULL. (Esta é a razão pela qual não podemos instanciar a classe abstrata).
Próxima entidade, _vptr, que é chamada de ponteiro vtable, é um ponteiro oculto que o compilador adiciona à classe base. Este _vptr aponta para a vtable da classe. Todas as classes derivadas desta classe base herdam o _vptr.
Cada objeto de uma classe que contém as funções virtuais armazena internamente esse _vptr e é transparente para o usuário. Cada chamada para função virtual usando um objeto é então resolvida usando este _vptr.
Vamos dar um exemplo para demonstrar o funcionamento de vtable e _vtr.
#include using namespace std; class Base_virtual { public: virtual void function1_virtual() {cout<<'Base :: function1_virtual()
';}; virtual void function2_virtual() {cout<<'Base :: function2_virtual()
';}; virtual ~Base_virtual(){}; }; class Derived1_virtual: public Base_virtual { public: ~Derived1_virtual(){}; virtual void function1_virtual() { coutfunction2_virtual(); delete (b); return (0); }
Resultado:
Derived1_virtual :: function1_virtual ()
Base :: function2_virtual ()
No programa acima, temos uma classe base com duas funções virtuais e um destruidor virtual. Também derivamos uma classe da classe base e desta; substituímos apenas uma função virtual. Na função principal, o ponteiro da classe derivada é atribuído ao ponteiro base.
Em seguida, chamamos ambas as funções virtuais usando um ponteiro de classe base. Vemos que a função substituída é chamada quando é chamada e não a função base. Enquanto no segundo caso, como a função não é substituída, a função da classe base é chamada.
Agora vamos ver como o programa acima é representado internamente usando vtable e _vptr.
Conforme a explicação anterior, como existem duas classes com funções virtuais, teremos duas vtables - uma para cada classe. Além disso, _vptr estará presente para a classe base.

Acima é mostrada a representação pictórica de como o layout vtable será para o programa acima. A vtable para a classe base é direta. No caso da classe derivada, apenas function1_virtual é substituído.
Portanto, vemos que na classe derivada vtable, o ponteiro de função para function1_virtual aponta para a função substituída na classe derivada. Por outro lado, o ponteiro de função para function2_virtual aponta para uma função na classe base.
Portanto, no programa acima, quando o ponteiro base é atribuído a um objeto de classe derivada, o ponteiro base aponta para _vptr da classe derivada.
Então, quando a chamada b-> function1_virtual () é feita, o function1_virtual da classe derivada é chamado e quando a chamada de função b-> function2_virtual () é feita, já que este ponteiro de função aponta para a função da classe base, a função da classe base é chamado.
Funções virtuais puras e classe abstrata
Vimos detalhes sobre funções virtuais em C ++ em nossa seção anterior. Em C ++, também podemos definir um “ função virtual pura ”Que geralmente é igualado a zero.
A função virtual pura é declarada conforme mostrado abaixo.
virtual return_type function_name(arg list) = 0;
A classe que tem pelo menos uma função virtual pura chamada de “ classe abstrata ”. Nunca podemos instanciar a classe abstrata, ou seja, não podemos criar um objeto da classe abstrata.
Isso porque sabemos que uma entrada é feita para cada função virtual no VTABLE (tabela virtual). Mas no caso de uma função virtual pura, esta entrada não tem nenhum endereço, o que a torna incompleta. Portanto, o compilador não permite a criação de um objeto para a classe com entrada VTABLE incompleta.
Esta é a razão pela qual não podemos instanciar uma classe abstrata.
O exemplo a seguir demonstrará a função virtual Pure, bem como a classe Abstract.
#include using namespace std; class Base_abstract { public: virtual void print() = 0; // Pure Virtual Function }; class Derived_class:public Base_abstract { public: void print() { cout <<'Overriding pure virtual function in derived class
'; } }; int main() { // Base obj; //Compile Time Error Base_abstract *b; Derived_class d; b = &d; b->print(); }
Resultado:
Substituindo função virtual pura na classe derivada
No programa acima, temos uma classe definida como Base_abstract que contém uma função virtual pura que a torna uma classe abstrata. Em seguida, derivamos uma classe “Derived_class” de Base_abstract e substituímos a função virtual pura print nela.
Na função principal, essa primeira linha não é comentada. Isso ocorre porque se descomentarmos, o compilador dará um erro, pois não podemos criar um objeto para uma classe abstrata.
Mas a partir da segunda linha o código funciona. Podemos criar com sucesso um ponteiro de classe base e então atribuir um objeto de classe derivada a ele. Em seguida, chamamos uma função de impressão que produz o conteúdo da função de impressão substituída na classe derivada.
Vamos listar algumas características da classe abstrata em poucas palavras:
- Não podemos instanciar uma classe abstrata.
- Uma classe abstrata contém pelo menos uma função virtual pura.
- Embora não possamos instanciar a classe abstrata, sempre podemos criar ponteiros ou referências para essa classe.
- Uma classe abstrata pode ter alguma implementação como propriedades e métodos junto com funções virtuais puras.
- Quando derivamos uma classe da classe abstrata, a classe derivada deve substituir todas as funções virtuais puras na classe abstrata. Se isso falhar, a classe derivada também será uma classe abstrata.
Destruidores Virtuais
Os destruidores da classe podem ser declarados como virtuais. Sempre que fazemos upcast, ou seja, atribuindo o objeto da classe derivada a um ponteiro da classe base, os destruidores comuns podem produzir resultados inaceitáveis.
Por exemplo,considere a seguinte atualização do destruidor comum.
#include using namespace std; class Base { public: ~Base() { cout << 'Base Class:: Destructor
'; } }; class Derived:public Base { public: ~Derived() { cout<< 'Derived class:: Destructor
'; } }; int main() { Base* b = new Derived; // Upcasting delete b; }
Resultado:
Classe Base :: Destruidor
No programa acima, temos uma classe derivada herdada da classe base. No principal, atribuímos um objeto da classe derivada a um ponteiro de classe base.
Idealmente, o destruidor que é chamado quando “delete b” é chamado deveria ser aquele da classe derivada, mas podemos ver na saída que o destruidor da classe base é chamado quando o ponteiro da classe base aponta para isso.
Devido a isso, o destruidor da classe derivada não é chamado e o objeto da classe derivada permanece intacto, resultando em um vazamento de memória. A solução para isso é tornar o construtor da classe base virtual de modo que o ponteiro do objeto aponte para o destruidor correto e a destruição adequada dos objetos seja realizada.
O uso do destruidor virtual é mostrado no exemplo abaixo.
#include using namespace std; class Base { public: virtual ~Base() { cout << 'Base Class:: Destructor
'; } }; class Derived:public Base { public: ~Derived() { cout<< 'Derived class:: Destructor
'; } }; int main() { Base* b = new Derived; // Upcasting delete b; }
Resultado:
Classe derivada :: Destructor
Classe Base :: Destruidor
Este é o mesmo programa que o programa anterior, exceto que adicionamos uma palavra-chave virtual na frente do destruidor da classe base. Ao tornar o destruidor da classe base virtual, alcançamos a saída desejada.
Podemos ver que quando atribuímos o objeto da classe derivada ao ponteiro da classe base e, em seguida, excluímos o ponteiro da classe base, os destruidores são chamados na ordem inversa da criação do objeto. Isso significa que primeiro o destruidor da classe derivada é chamado e o objeto é destruído e, em seguida, o objeto da classe base é destruído.
Observação: Em C ++, os construtores nunca podem ser virtuais, pois os construtores estão envolvidos na construção e inicialização dos objetos. Portanto, precisamos que todos os construtores sejam executados completamente.
Conclusão
O polimorfismo de tempo de execução é implementado usando a substituição de método. Isso funciona bem quando chamamos os métodos com seus respectivos objetos. Mas quando temos um ponteiro da classe base e chamamos métodos substituídos usando o ponteiro da classe base apontando para os objetos da classe derivada, ocorrem resultados inesperados devido à vinculação estática.
Para superar isso, usamos o conceito de funções virtuais. Com a representação interna de vtables e _vptr, as funções virtuais nos ajudam a chamar com precisão as funções desejadas. Neste tutorial, vimos em detalhes sobre o polimorfismo de tempo de execução usado em C ++.
Com isso, concluímos nossos tutoriais sobre programação orientada a objetos em C ++. Esperamos que este tutorial seja útil para obter uma compreensão melhor e completa dos conceitos de programação orientada a objetos em C ++.
=> Visite aqui para aprender C ++ do zero.
Leitura recomendada
- Polimorfismo em C ++
- Herança em C ++
- Funções de amigo em C ++
- Classes e objetos em C ++
- Uso de Selenium Select Class para lidar com elementos suspensos em uma página da Web - Selenium Tutorial # 13
- Tutorial da função principal do Python com exemplos práticos
- Java Virtual Machine: como a JVM ajuda na execução de aplicativos Java
- Como configurar arquivos de script e configurações de tempo de execução do LoadRunner VuGen