Função virtual

Da Wikipédia, a enciclopédia livre
Ir para a navegação Saltar para pesquisar

Na programação orientada a objetos , em linguagens como C++ e Object Pascal , uma função virtual ou método virtual é uma função ou método herdável e substituível para o qual o despacho dinâmico é facilitado. Esse conceito é uma parte importante da porção de polimorfismo (tempo de execução) da programação orientada a objetos (OOP). Em resumo, uma função virtual define uma função de destino a ser executada, mas o destino pode não ser conhecido em tempo de compilação.

A maioria das linguagens de programação, como JavaScript , PHP e Python , tratam todos os métodos como virtuais por padrão [1] [2] e não fornecem um modificador para alterar esse comportamento. No entanto, algumas linguagens fornecem modificadores para evitar que métodos sejam substituídos por classes derivadas (como a palavra-chave final em Java [3] e PHP [4] ).

Objetivo

O conceito da função virtual resolve o seguinte problema:

Na programação orientada a objetos , quando uma classe derivada herda de uma classe base, um objeto da classe derivada pode ser referenciado por meio de um ponteiro ou referência do tipo de classe base em vez do tipo de classe derivada. Se houver métodos de classe base substituídos pela classe derivada, o método realmente chamado por tal referência ou ponteiro pode ser vinculado (vinculado) 'early' (pelo compilador), de acordo com o tipo declarado do ponteiro ou referência, ou 'atrasado' (ou seja, pelo sistema de tempo de execução da linguagem), de acordo com o tipo real do objeto é referido.

As funções virtuais são resolvidas 'atrasadas'. Se a função em questão for 'virtual' na classe base, a implementação da função da classe mais derivada é chamada de acordo com o tipo real do objeto referido, independentemente do tipo declarado do ponteiro ou referência. Se não for 'virtual', o método é resolvido 'precoce' e selecionado de acordo com o tipo declarado do ponteiro ou referência.

As funções virtuais permitem que um programa chame métodos que nem necessariamente existem no momento em que o código é compilado. [ citação necessária ]

Em C++, os métodos virtuais são declarados ao prefixar a palavra-chave à declaração da função na classe base. Esse modificador é herdado por todas as implementações desse método em classes derivadas, o que significa que elas podem continuar a substituir umas às outras e ser vinculadas tardiamente. E mesmo que os métodos pertencentes à classe base chamem o método virtual, eles estarão chamando o método derivado. A sobrecarga ocorre quando dois ou mais métodos em uma classe têm o mesmo nome de método, mas parâmetros diferentes. Substituir significa ter dois métodos com o mesmo nome e parâmetros de método. A sobrecarga também é conhecida como correspondência de função e a substituição como mapeamento de função dinâmico. virtual

Exemplo

C++

Diagrama de Classe do Animal

Por exemplo, uma classe base Animalpode ter uma função virtual Eat. Subclass Llamaseria implementada Eatde forma diferente de subclass Wolf, mas pode-se invocar Eatem qualquer instância de classe referida como Animal e obter o Eatcomportamento da subclasse específica.

classe  Animal { 
 público :
  // Intencionalmente não virtual: 
void Move () {    
    std :: cout << "Este animal se move de alguma forma" << std :: endl ;    
  }
  vazio virtual Comer () = 0 ;    
};

// A classe "Animal" pode possuir uma definição para Eat se desejado. 
class  Llama : public Animal {    
 público :
  // A função não virtual Move é herdada, mas não substituída. 
void Comer () substituir {     
    std :: cout << "Lhamas comem grama!" << std :: endl ;    
  }
};

Isso permite que um programador processe uma lista de objetos de classe Animal, dizendo a cada um para comer (chamando Eat), sem precisar saber que tipo de animal pode estar na lista, como cada animal come ou qual o conjunto completo de possíveis tipos de animais podem ser.

Em C, o mecanismo por trás das funções virtuais pode ser fornecido da seguinte maneira:

#include <stdio.h> 

/* um objeto aponta para sua classe... */
estrutura  Animal { 
    const struct AnimalClass * class ;    
};

/* que contém a função virtual Animal.Eat */
struct  AnimalClass { 
    void ( * Comer )( estrutura Animal * ); // função 'virtual' };    


/* Como Animal.Move não é uma função virtual, 
   ela não está na estrutura acima. */
void Move ( struct Animal * self )    
{
    printf ( "<Animal em %p> movido de alguma forma \n " , ( void * ) self );   
}

/* diferentemente de Move, que executa Animal.Move diretamente, 
   Eat não pode saber qual função (se houver) chamar em tempo de compilação. 
   Animal.Eat só pode ser resolvido em tempo de execução quando Eat é chamado. */
void Comer ( struct Animal * self )    
{
    const struct AnimalClass * class = * ( const void ** ) self ;         
    if ( class -> Eat ) class -> Eat ( self ); // executa Animal.Eat else  
         
    
        fprintf ( stderr , "Comer não implementado \n " ); 
}

/* implementação de Llama.Eat esta é a função alvo 
   a ser chamada por 'void Eat(struct Animal *).' */
static void _Llama_eat ( struct Animal * self )     
{
    printf ( "<Lhama em %p> Lhama come grama! \n " , ( void * ) self ); }       


/* inicializa a classe */
const struct AnimalClass Animal = {( void * ) 0 }; // classe base não implementa Animal.Eat const struct AnimalClass Llama = { _Llama_eat }; // mas a classe derivada faz        
       

int main ( void ) 
{
   /* objetos init como instância de sua classe */
   struct  Animal animal = { & Animal };    
   struct  Lhama animal = { & Lhama };    
   Mover ( & animal ); // Animal.Move Move ( & llama ); // Llama.Move Comer ( & animal ); // não pode resolver Animal.Eat então imprima "Não implementado" para stderr Eat ( & llama ); // resolve Llama.Eat e executa }  
      
      
       

Classes abstratas e funções virtuais puras

Uma função virtual pura ou método virtual puro é uma função virtual que deve ser implementada por uma classe derivada se a classe derivada não for abstrata . As classes que contêm métodos virtuais puros são chamadas de "abstratas" e não podem ser instanciadas diretamente. Uma subclasse de uma classe abstrata só pode ser instanciada diretamente se todos os métodos virtuais puros herdados tiverem sido implementados por essa classe ou por uma classe pai. Métodos virtuais puros normalmente têm uma declaração ( assinatura ) e nenhuma definição ( implementação ).

Como exemplo, uma classe base abstrata MathSymbolpode fornecer uma função virtual pura doOperation()e classes derivadas Pluse Minusimplementar doOperation()para fornecer implementações concretas. A implementação doOperation()não faria sentido na MathSymbolclasse, pois MathSymbolé um conceito abstrato cujo comportamento é definido apenas para cada tipo (subclasse) de MathSymbol. Da mesma forma, uma determinada subclasse de MathSymbolnão estaria completa sem uma implementação de doOperation().

Embora os métodos virtuais puros normalmente não tenham implementação na classe que os declara, métodos virtuais puros em algumas linguagens (por exemplo, C++ e Python) podem conter uma implementação em sua classe declarante, fornecendo um comportamento padrão ou de fallback que uma classe derivada pode delegar para , se apropriado. [5] [6]

Funções virtuais puras também podem ser usadas onde as declarações de método estão sendo usadas para definir uma interface - semelhante ao que a palavra-chave de interface em Java especifica explicitamente. Nesse uso, as classes derivadas fornecerão todas as implementações. Nesse padrão de projeto , a classe abstrata que serve como interface conterá apenas funções virtuais puras, mas nenhum membro de dados ou métodos comuns. Em C++, usar classes puramente abstratas como interfaces funciona porque C++ suporta herança múltipla . No entanto, como muitas linguagens OOP não suportam herança múltipla, elas geralmente fornecem um mecanismo de interface separado. Um exemplo é a linguagem de programação Java .

Comportamento durante a construção e destruição

As linguagens diferem em seu comportamento enquanto o construtor ou destruidor de um objeto está em execução. Por esse motivo, chamar funções virtuais em construtores geralmente é desencorajado.

Em C++, a função "base" é chamada. Especificamente, a função mais derivada que não é mais derivada do que a classe do construtor ou destruidor atual é chamada. [7] : §15.7.3  [8] [9] Se essa função for uma função virtual pura, ocorrerá um comportamento indefinido . [7] : §13.4.6  [8] Isso é verdade mesmo se a classe contiver uma implementação para essa função virtual pura, pois uma chamada para uma função virtual pura deve ser explicitamente qualificada. [10] Uma implementação compatível com C++ não é necessária (e geralmente não é capaz) para detectar chamadas indiretas para funções virtuais puras em tempo de compilação ou tempo de link . Algumos sistemas de tempo de execução emitirão um erro de chamada de função virtual pura ao encontrar uma chamada para uma função virtual pura em tempo de execução .

Em Java e C#, a implementação derivada é chamada, mas alguns campos ainda não foram inicializados pelo construtor derivado (embora sejam inicializados com seus valores zero padrão). [11] Alguns padrões de projeto , como o Abstract Factory Pattern , promovem ativamente esse uso em linguagens que suportam essa capacidade.

Destruidores virtuais

As linguagens orientadas a objetos normalmente gerenciam a alocação e desalocação de memória automaticamente quando os objetos são criados e destruídos. No entanto, algumas linguagens orientadas a objetos permitem que um método destruidor personalizado seja implementado, se desejado. Se a linguagem em questão usa gerenciamento automático de memória, o destruidor personalizado (geralmente chamado de finalizador neste contexto) que é chamado certamente será o apropriado para o objeto em questão. Por exemplo, se um objeto do tipo Wolf que herda Animal for criado, e ambos tiverem destruidores personalizados, aquele chamado será aquele declarado em Wolf.

Em contextos de gerenciamento manual de memória, a situação pode ser mais complexa, principalmente em relação ao despacho estático . Se um objeto do tipo Wolf é criado, mas apontado por um ponteiro Animal, e é esse tipo de ponteiro Animal que é excluído, o destruidor chamado pode ser o definido para Animal e não o para Wolf, a menos que o destruidor seja virtual . Este é particularmente o caso de C++, onde o comportamento é uma fonte comum de erros de programação se os destruidores não forem virtuais.

Veja também

Referências

  1. ^ "Polymorphism (The Java™ Tutorials > Learning the Java Language > Interfaces and Heritance)" . docs.oracle.com . Recuperado 2020-07-11 .
  2. ^ "9. Classes — documentação do Python 3.9.2" . docs.python.org . Recuperado 2021-02-23 .
  3. ^ "Escrevendo Classes e Métodos Finais (Os Tutoriais Java™ > Aprendendo a Linguagem Java > Interfaces e Herança)" . docs.oracle.com . Recuperado 2020-07-11 .
  4. ^ "PHP: Palavra-chave final - Manual" . www.php.net . Recuperado 2020-07-11 .
  5. ^ Destruidores virtuais puros - cppreference.com
  6. ^ "abc — Classes básicas abstratas: @abc.abstractmethod"
  7. ^ a b "N4659: Esboço de trabalho, padrão para linguagem de programação C++" (PDF) .
  8. ^ a b Chen, Raymond (28 de abril de 2004). "O que é __purecall?" .
  9. ^ Meyers, Scott (6 de junho de 2005). "Nunca chame funções virtuais durante a construção ou destruição" .
  10. ^ Chen, Raymond (11 de outubro de 2013). "Caso de canto C++: você pode implementar funções virtuais puras na classe base" .
  11. ^ Ganesh, SG (1 de agosto de 2011). "Alegria de Programação: Chamando Funções Virtuais de Construtores" .