Funções Virtuais em C++: por que não dá para viver sem elas?
\n\nImagine que você é um diretor filmando um filme sobre animais. Você tem um roteiro: "Todos os animais emitem um som". Você filma uma cena com um cachorro — ele late. Uma cena com um gato — ele mia. Tudo lógico. Agora imagine que seu assistente (o compilador) traz uma caixa de animais de brinquedo e diz: "Filme!". Você aperta o botão, e o brinquedo... fica em silêncio. Por quê? Porque você esqueceu as funções virtuais.
\n\nNa programação em C++, esse problema aparece o tempo todo. Você escreve a classe "Animal", dentro dela o método sound(). Depois cria as classes "Cachorro" e "Gato", que herdam de "Animal". E parece que está tudo bem, até você tentar trabalhar com esses objetos através de um ponteiro para a classe base. É aí que descobre que o C++, por padrão, chama o método da classe base, e não o sobrescrito na classe derivada. As funções virtuais resolvem esse problema de uma vez por todas.
Neste artigo, vamos detalhar o que são funções virtuais, para que servem, como funcionam internamente e por que são indispensáveis no C++ moderno.
\n\nO que é uma função virtual em termos simples
\n\nFunção virtual é um método de classe que você marca com a palavra-chave virtual. Essa função pode ser sobrescrita em classes derivadas, e, ao ser chamada através de um ponteiro ou referência para a classe base, a versão da classe derivada será executada.
Parece complicado? Vamos a um exemplo da vida real. Imagine um controle remoto para robôs. Você tem uma classe base "Robô" com o método doWork(). Existem um robô aspirador e um robô entregador. Se você aperta o botão "Trabalhar", os algoritmos correspondentes devem ser executados: o aspirador começa a limpar, o entregador começa a entregar um pacote. Sem funções virtuais, você apertaria o botão e o robô simplesmente ficaria parado (o método da classe base seria chamado).
Em C++, as funções virtuais são a base do polimorfismo. O polimorfismo permite trabalhar de forma uniforme com objetos de tipos diferentes, desde que eles herdem de um ancestral comum.
\n\nO problema sem funções virtuais: um exemplo prático
\n\nVamos ver o que acontece se não usarmos virtual.
#include <iostream>\n#include <vector>\n\nclass Animal {\npublic:\n void sound() {\n std::cout << "Some generic animal sound" << std::endl;\n }\n};\n\nclass Dog : public Animal {\npublic:\n void sound() {\n std::cout << "Woof! Woof!" << std::endl;\n }\n};\n\nclass Cat : public Animal {\npublic:\n void sound() {\n std::cout << "Meow!" << std::endl;\n }\n};\n\nint main() {\n std::vector<Animal*> animals;\n animals.push_back(new Dog());\n animals.push_back(new Cat());\n\n for (Animal* a : animals) {\n a->sound(); // O que será exibido?\n }\n\n // Limpeza de memória\n for (Animal* a : animals) delete a;\n\n return 0;\n}\n\n\nExecute este código. Você verá:
\nSome generic animal sound\nSome generic animal sound\n\n\nPor quê? Porque o compilador olha para o tipo do ponteiro (Animal*), e não para o objeto real. Ele chama o método da classe base. Isso é chamado de ligação antecipada (early binding) — a decisão sobre qual método chamar é tomada em tempo de compilação.
Nós queremos ligação tardia (late binding) — que a decisão seja tomada durante a execução do programa, dependendo do tipo do objeto. É exatamente isso que as funções virtuais proporcionam.
\n\nSolução: adicionando virtual
\n\nVamos corrigir o exemplo adicionando a palavra-chave virtual.
class Animal {\npublic:\n virtual void sound() { // Adicionamos virtual\n std::cout << "Some generic animal sound" <<