Diferença entre Funções Virtuais e Sobrecarga de funções em C++

🗞️ O conceito pode atrapalhar o entendimento de alguns.


Diferença entre Funções Virtuais e Sobrecarga de funções em C++


Um dos inscritos do Curso de C++ Moderno questionou a diferença entre Funções-membro Virtuais e Sobrecarga de Funções. Eu respondi, no entanto, os recursos para ilustrar a explicação são meio limitados lá na Udemy. Como são dois assuntos distintos, infelizmente não fiz um vídeo que compara os dois casos.

Acabei enviando alguns links, mas além de não ser em Português, os exemplos de muitos endereços não são tão bons assim. Então, eu preferi criar esse artigo com meus próprios exemplos e ficar mais fácil de entender!

Funções virtuais em C++ são funções membro de uma classe base que podem ser substituídas (overridden) por funções membro de uma classe derivada.

Elas são declaradas usando a palavra-chave virtual na classe base.

O principal objetivo das funções virtuais é permitir o polimorfismo em tempo de execução, ou seja, permitir que a chamada a uma função membro seja resolvida em tempo de execução com base no tipo do objeto para o qual a função está sendo chamada, e não no tipo da referência ou ponteiro que está sendo usado para acessar o objeto.

Aqui estão alguns exemplos para ilustrar o conceito de funções virtuais:

Observação: Funções-membro virtuais NÃO FUNCIONA COM TEMPLATE, e lógico, só funcionam em Programação Orientada a Objetos!


Exemplo Básico de Funções Virtuais

Veja nesse exemplo que a classe Derivada herda os membros public(e se houvessem, os protected também) da classe Base.

Note que ambas possuem a função-membro: void show(), e mesmo usando ponteiros(tanto faz ser ou não smart pointers, é a mesma coisa) para criar o objeto, ao chamar show(), o conteúdo da Base é que é exibido:

#include <iostream>

class Base {
  public:
    void show() {
      std::cout << "Eu sou o show() da classe Base.\n";
    }
    // Destrutor para permitir a limpeza adequada de objetos derivados
    ~Base() = default;
};

class Derivada : public Base {
  public:
    void show() {
      std::cout << "AGORA SIM! Eu sou o show() da classe Derivada.\n";
    }
};

int main() {
  Base * base;
  Derivada derivada;
  base = &derivada;

  // A função show() da classe Derivada é chamada, embora o ponteiro seja de tipo Base*
  base->show();

  return 0;
}

Saída após compilar e rodar g++ nao-virtual.cpp && ./a.out:

Eu sou o show() da classe Base.

Para conseguirmos exibir o show() da Derivada, bastava declararmos a show() da Base como virtual, mas para sabermos que estamos sobreescrvendo uma função-membro virtual, o correto é também declararmos o show() da Derivada com a palavra-chave overridde, ou seja, nosso código ficaria assim:

OBSERVAÇÃO: Somente nos casos em que o objeto é criado com ponteiros, como foi dito acima!

#include <iostream>

class Base {
  public:
    virtual void show() {
      std::cout << "Eu sou o show() da classe Base.\n";
    }
    // Destrutor para permitir a limpeza adequada de objetos derivados
    ~Base() = default;
};

class Derivada : public Base {
  public:
    void show() override {
      std::cout << "AGORA SIM! Eu sou o show() da classe Derivada.\n";
    }
};

int main() {
  Base * base;
  Derivada derivada;
  base = &derivada;

  // A função show() da classe Derived é chamada, embora o ponteiro seja de tipo Base*
  base->show();

  return 0;
}

Agora sim, a saída após compilar e rodar g++ nao-virtual.cpp && ./a.out será o show() da Derivada:

AGORA SIM! Eu sou o show() da classe Derivada.

Esse conceito também é muito utilizado como Destrutores virtuais, ou seja, para seu software não ficar chamando vários Destrutores recursivamente. Por isso note que eu declarei: ~Base() = default, mas se tivéssemos declarando como: virtual ~Base();, o destrutor da Derivada, se houvesse, prevaleceria!

Isso é importante também para evitar vazamentos de memória e outros problemas relacionados à limpeza adequada dos recursos, principalmente em casos de múltiplas hieraquias de classes, ex,: class Shape(); → class RectangleShape(); → class SquareShape();.

Funções virtuais são um mecanismo fundamental para o polimorfismo em C++ e permitem que você escreva código mais flexível e extensível.


Sobrecarga de funções

Um outro conceito em C++ é Sobrecarga de funções, que em resumo é: “Usar funções de mesmo nome, mas com tipos parâmetros diferentes, mas só diferença de tipo de retorno, não!”.

Exemplo, note que abaixo temos as funções show() de mesmo nome mas tipos de parâmetros diferentes:

#include <iostream>

void show(){
  std::cout << "void sem parâmetros\n";
}

void show(const std::string& str){
  std::cout << "str é: " << str << '\n';
}

std::string show(const std::string& str, int c){
  return str;
}

int main() {
  void();
  void("Oi.");
  std::cout << show("Olá!", 1) << '\n';
}

Isso compila e roda!

No entanto se tivéssemos essa função:

void show(const std::string& str, int c){  // ■ Functions that differ only in their return type cannot be overloaded
  std::cout << "message" << '\n';
}

Não compilará, pois apesar de ter um tipo de retorno diferente: void, ela possui os mesmos argumentos que uma função que já existe que é a : std::string show(const std::string& str, int c);.

Isso funciona também para funções-membro em Programação Orientada a Objetos!


Note que Funções Virtuais é TOTALMENTE diferente de Sobrecarga de funções. Talvez a similaridade que alguns podem ver é o fato de usar mesmo nome de funções.

Espero ter ajudado! E em um futuro próximo pretendo alterar e adicionar vários vezes dos Cursos de C++, com exemplos ainda melhores!


cpp cppdaily


Compartilhe


Nosso canal no Youtube

Inscreva-se


Marcos Oliveira

Marcos Oliveira

Desenvolvedor de software
https://github.com/terroo


Crie Aplicativos Gráficos para Linux e Windows com C++

Aprenda C++ Moderno e crie Games, Programas CLI, GUI e TUI de forma fácil.

Saiba Mais

Receba as novidades no seu e-mail!

Após cadastro e confirmação do e-mail, enviaremos semanalmente resumos e também sempre que houver novidades por aqui para que você mantenha-se atualizado!