Menu
Coddy logo textTech

Funções virtuais revisadas

Parte da seção Programação Orientada a Objetos do Journey de C++ da Coddy. Lição 58 de 104.

Agora que entendemos a diferença entre o polimorfismo em tempo de compilação e o polimorfismo em tempo de execução, vamos analisar mais detalhadamente as funções virtuais e a palavra-chave override, que torna seu uso mais seguro.

Quando você marca uma função como virtual em uma classe-base, as classes derivadas podem fornecer sua própria implementação. O especificador override informa explicitamente ao compilador que você pretende substituir uma função virtual:

class Animal {
public:
    virtual void speak() {
        std::cout << "Some sound" << std::endl;
    }
    virtual ~Animal() = default;
};

class Dog : public Animal {
public:
    void speak() override {
        std::cout << "Woof!" << std::endl;
    }
};

Usar override é essencial porque detecta erros em tempo de compilação. Se você acidentalmente escrever o nome da função errado ou usar parâmetros incorretos, o compilador alertará você em vez de criar silenciosamente uma nova função:

class Cat : public Animal {
public:
    void speek() override {  // Erro do compilador: nenhuma função para sobrescrever
        std::cout << "Meow!" << std::endl;
    }
};

O especificador final impede novas substituições. Use-o quando uma classe derivada deve ser a última a substituir uma determinada função:

class Bulldog : public Dog {
public:
    void speak() override final {
        std::cout << "Gruff woof!" << std::endl;
    }
};

class TinyBulldog : public Bulldog {
    void speak() override {}  // Erro: não é possível sobrescrever a função final
};

Sempre use override ao sobrescrever funções virtuais. Isso documenta sua intenção e permite que o compilador verifique se você está realmente sobrescrevendo uma função virtual existente.

challenge icon

Desafio

Fácil

Vamos criar um sistema de notificações que demonstra o poder das palavras-chave override e final com funções virtuais. Você criará uma hierarquia de manipuladores de notificações, na qual alguns métodos podem ser personalizados posteriormente e outros são bloqueados para impedir modificações.

Você organizará seu código em três arquivos:

  • Notifier.h: defina uma classe base Notifier que represente qualquer remetente de notificações:
    • Um membro protegido std::string recipient
    • Um construtor que recebe e armazena o nome do destinatário
    • Um método virtual send(const std::string& message) que imprime: Notifying <recipient>: <message>
    • Um método virtual getType() que retorna a string "Generic"
    • Um destrutor virtual
  • EmailNotifier.h: defina uma classe EmailNotifier que herda de Notifier:
    • Um membro privado std::string domain
    • Um construtor que recebe o destinatário e o domínio, passando o destinatário para a classe base
    • Substitua send() para imprimir: Emailing <recipient>@<domain>: <message>
    • Substitua getType() e marque-o como final. Ele deve retornar "Email"

    Em seguida, defina uma classe UrgentEmailNotifier que herda de EmailNotifier:

    • Um construtor que recebe o destinatário e o domínio, passando ambos para EmailNotifier
    • Substitua send() para imprimir: [URGENT] Emailing <recipient>@<domain>: <message>
    • Observação: você não pode substituir getType() aqui porque ele foi marcado como final em EmailNotifier
  • main.cpp: leia três entradas (cada uma em uma linha separada):
    1. Nome do destinatário
    2. Domínio do e-mail
    3. Texto da mensagem

    Crie dinamicamente três objetos de notificação: um Notifier base, um EmailNotifier e um UrgentEmailNotifier, todos usando o mesmo destinatário (e o mesmo domínio quando aplicável). Armazene-os em um array de ponteiros Notifier*.

    Percorra o array e, para cada notificador, imprima seu tipo usando getType() e, em seguida, chame send() com sua mensagem. Formate cada entrada como:

    Type: <type>
    <send output>

    Imprima uma linha em branco entre cada notificador. Libere os objetos alocados dinamicamente ao terminar.

Por exemplo, com as entradas Alice, company.com e Meeting at 3pm:

Type: Generic
Notifying Alice: Meeting at 3pm

Type: Email
Emailing Alice@company.com: Meeting at 3pm

Type: Email
[URGENT] Emailing Alice@company.com: Meeting at 3pm

Observe como UrgentEmailNotifier pode substituir send() para personalizar o formato da mensagem, mas herda o tipo "Email" de EmailNotifier porque getType() foi marcado como final. Use a palavra-chave override em todos os métodos substituídos para detectar incompatibilidades de assinatura em tempo de compilação.

Experimente você mesmo

#include <iostream>
#include <string>
#include "Notifier.h"
#include "EmailNotifier.h"

using namespace std;

int main() {
    // Ler entradas
    string recipient;
    string domain;
    string message;
    
    getline(cin, recipient);
    getline(cin, domain);
    getline(cin, message);
    
    // TODO: Crie um array de ponteiros Notifier* com 3 elementos
    
    // TODO: Crie dinamicamente:
    // - Um Notifier base com o recipient
    // - Um EmailNotifier com recipient e domain
    // - Um UrgentEmailNotifier com recipient e domain
    
    // TODO: Percorra o array e para cada notifier:
    // - Imprima "Type: " seguido do resultado de getType()
    // - Chame send() com a message
    // - Imprima uma linha em branco entre os notifiers (não após o último)
    
    // TODO: Limpe os objetos alocados dinamicamente
    
    return 0;
}
quiz iconTeste seus conhecimentos

Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.

Todas as lições de Programação Orientada a Objetos

Pratique por conta própria: Compilador de C++ online