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.
Desafio
FácilVamos 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 baseNotifierque 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
- Um membro protegido
EmailNotifier.h: Defina uma classeEmailNotifierque herda deNotifier:- 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 comofinal— ele deve retornar"Email"
Em seguida, defina uma classe
UrgentEmailNotifierque herda deEmailNotifier:- 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 comofinalemEmailNotifier
- Um membro privado
main.cpp: Leia três entradas (cada uma em uma linha separada):- Nome do destinatário
- Domínio de e-mail
- Texto da mensagem
Crie três objetos notificadores dinamicamente: um
Notifierbase, umEmailNotifiere umUrgentEmailNotifier— todos usando o mesmo destinatário (e domínio, onde aplicável). Armazene-os em um array de ponteirosNotifier*.Percorra o array e, para cada notificador, imprima seu tipo usando
getType(), depois chamesend()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 3pmObserve 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;
}
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
1Fundamentos de OOP
Arquivos ExternosBuild e Compilação em C++Arquivos Header e SourceNamespaces e EscopoIntrodução a OOP em C++Classes vs ObjetosO Ponteiro 'this'Métodos (Funções Membro)Atributos (Membros de Dados)Básico de Ctors e DtorsRecapitulação - Calculadora Simples4Propriedades de Classe
Membros de Instância vs EstáticosGetters e SettersFunções de Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRecapitulação - Gerenciador de Conta Bancária7Herança
Herança BásicaNíveis de Acesso na HerançaOrdem de Chamada de Ctor e DtorSobrescrita de MétodosFunções Virtuais e VTableHerança MúltiplaHerança VirtualRecapitulação - Hierarquia de Funcionários2Gerenciamento de Memória
Memória Stack vs HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Smart Pointers em C++RAII em C++Recapitulação - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de Acesso em C++Especificadores de Acesso em DetalhesOcultação de InformaçãoStruct vs ClassClasses Aninhadas e InternasRecapitulação - Sistema de Registros de Alunos8Polimorfismo
Polimorfismo de Compilação vs Tempo de ExecuçãoSobrecarga de FunçõesFunções Virtuais RevisitadasFunções Virtuais PurasClasses AbstratasDesign de Interfaces em C++Dynamic Casting e RTTIRecapitulação - Calculadora de Formas3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentaçãoListas de Inicialização do ConstrutorConstrutores DelegadosMergulho Profundo em DestrutoresRegra dos Três / Cinco / ZeroRecapitulação - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de StreamSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçãoTemplates de ClasseEspecialização de TemplatesTemplates VariádicosFundamentos de SFINAE e Type TraitsRecapitulação - Container Genérico