Menu
Coddy logo textTech

Funções Virtuais Revisitadas

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 polimorfismo de tempo de compilação e de tempo de execução, vamos dar uma olhada mais profunda nas funções virtuais e na palavra-chave override que as torna mais seguras de usar.

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 sobrescrever 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 é crucial porque captura erros em tempo de compilação. Se você acidentalmente escrever incorretamente o nome da função ou usar parâmetros errados, o compilador irá alertá-lo em vez de criar silenciosamente uma nova função:

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

O especificador final impede novas sobrescritas. Use-o quando uma classe derivada deve ser a última a sobrescrever uma função específica:

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 uma função final
};

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

challenge icon

Desafio

Fácil

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

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

  • Notifier.h: Defina uma classe base Notifier que representa qualquer remetente de notificação:
    • 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
    • Sobrescreva send() para imprimir: Emailing <recipient>@<domain>: <message>
    • Sobrescreva 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
    • Sobrescreva send() para imprimir: [URGENT] Emailing <recipient>@<domain>: <message>
    • Nota: Você não pode sobrescrever 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 de e-mail
    3. Texto da mensagem

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

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

    Type: <type>
    <send output>

    Imprima uma linha em branco entre cada notificador. Limpe seus objetos alocados dinamicamente quando 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 o UrgentEmailNotifier pode sobrescrever 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 sobrescritos para capturar quaisquer incompatibilidades de assinatura em tempo de compilação.

Folha de consulta

O especificador override informa explicitamente ao compilador que você pretende sobrescrever uma função virtual de uma classe base:

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;
    }
};

O uso de override captura erros em tempo de compilação. Se você escrever incorretamente o nome da função ou usar parâmetros errados, o compilador irá alertá-lo:

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

O especificador final impede novas sobrescritas em classes derivadas:

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 uma função final
};

Sempre use override ao sobrescrever funções virtuais para documentar sua intenção e permitir a verificação do compilador.

Experimente você mesmo

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

using namespace std;

int main() {
    // Ler as entradas
    string recipient;
    string domain;
    string message;
    
    getline(cin, recipient);
    getline(cin, domain);
    getline(cin, message);
    
    // TODO: Criar um array de ponteiros Notifier* com 3 elementos
    
    // TODO: Criar dinamicamente:
    // - Um Notifier base com o recipient
    // - Um EmailNotifier com recipient e domain
    // - Um UrgentEmailNotifier com recipient e domain
    
    // TODO: Percorrer o array e para cada notificador:
    // - Imprimir "Type: " seguido pelo resultado de getType()
    // - Chamar send() com a message
    // - Imprimir uma linha em branco entre os notificadores (não após o último)
    
    // TODO: Limpar 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