Função virtual - Virtual function

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. Este conceito é uma parte importante da parte do polimorfismo (tempo de execução) da programação orientada a objetos (OOP). Resumindo, uma função virtual define uma função de destino a ser executada, mas o destino pode não ser conhecido no momento da compilação.

A maioria das linguagens de programação, como Java , PHP e Python , tratam todos os métodos como virtuais por padrão 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 e PHP ).

Propósito

O conceito de 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 referido 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) 'antecipadamente' (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 'tarde'. Se a função em questão for 'virtual' na classe base, a implementação da classe mais derivada da função é 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 'antecipadamente' 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.

Em C ++, os métodos virtuais são declarados adicionando-se a palavra-chave à declaração da função na classe base. Este modificador é herdado por todas as implementações desse método em classes derivadas, o que significa que elas podem continuar a se sobreporem e serem 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 de método e parâmetros. A sobrecarga também é conhecida como correspondência de função e substituição como mapeamento de função dinâmica. virtual

Exemplo

Diagrama de Classe do Animal

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

class Animal {
 public:
  // Intentionally not virtual:
  void Move(void) {
    std::cout << "This animal moves in some way" << std::endl;
  }
  virtual void Eat(void) = 0;
};

// The class "Animal" may possess a definition for Eat if desired.
class Llama : public Animal {
 public:
  // The non virtual function Move is inherited but not overridden.
  void Eat(void) override {
    std::cout << "Llamas eat grass!" << std::endl;
  }
};

Isso permite que um programador processe uma lista de objetos de classe Animal, dizendo a cada um por sua vez 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 os tipos de animais podem ser.

Podemos ver melhor como as funções virtuais funcionam implementando o exemplo acima em C

#include <stdio.h>

/* an object points to its class... */
struct Animal {
    const struct AnimalClass * class;
};

/* which contains the virtual function Animal.Eat */
struct AnimalClass {
    void (*Eat)(struct Animal *); // 'virtual' function 
};

/* Since Animal.Move is not a virtual function
   it is not in the structure above. */
void Move(struct Animal * self)
{
    printf("<Animal at %p> moved in some way\n", (void *) self);
}

/* unlike Move, which executes Animal.Move directly,
   Eat cannot know which function (if any) to call at compile time.
   Animal.Eat can only be resolved at run time when Eat is called. */
void Eat(struct Animal * self)
{
    const struct AnimalClass * class = *(const void **) self;
    if (class->Eat) 
        class->Eat(self); // execute Animal.Eat
    else
        fprintf(stderr, "Eat not implemented\n");
}

/* implementation of Llama.Eat this is the target function 
   to be called by 'void Eat(struct Animal *).' */
static void _Llama_eat(struct Animal * self)
{
    printf("<Llama at %p> Llama's eat grass!\n", (void *) self);    
}

/* initialize class */
const struct AnimalClass Animal = {(void *) 0}; // base class does not implement Animal.Eat
const struct AnimalClass Llama = {_Llama_eat};  // but the derived class does

int main(void)
{
   /* init objects as instance of its class */
   struct Animal animal = {& Animal};
   struct Animal llama = {& Llama};
   Move(& animal); // Animal.Move
   Move(& llama);  // Llama.Move
   Eat(& animal);  // cannot resolve Animal.Eat so print "Not Implemented" to stderr
   Eat(& llama);   // resolves Llama.Eat and executes
}

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 uma classe pai. Os 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. Implementar 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, os métodos virtuais puros em algumas linguagens (por exemplo, C ++ e Python) podem conter uma implementação em sua classe de declaração, fornecendo fallback ou comportamento padrão que uma classe derivada pode delegar , se apropriado.

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 interface em Java especifica explicitamente. Nesse uso, as classes derivadas fornecerão todas as implementações. Em tal um padrão de design , a classe abstrata que serve como uma interface conterá única funções puras virtual, mas há membros de dados ou métodos comuns. Em C ++, usar essas classes puramente abstratas como interfaces funciona porque C ++ oferece suporte a herança múltipla . No entanto, como muitas linguagens OOP não oferecem suporte a 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, a chamada de funções virtuais em construtores geralmente é desencorajada.

Em C ++, a função "base" é chamada. Especificamente, a função mais derivada que não é mais derivada do que a classe do construtor atual é chamada. Se essa função for uma função virtual pura, ocorrerá um comportamento indefinido . Isso é verdadeiro mesmo se a classe contiver uma implementação para essa função virtual pura. Uma implementação C ++ em conformidade 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 . Alguns 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). Alguns padrões de projeto , como o Abstract Factory Pattern , promovem ativamente esse uso em linguagens que oferecem suporte a essa capacidade.

Destruidores virtuais

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 é 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, o chamado será o declarado em Wolf.

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

Veja também

Referências