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.
Desafio
FácilVamos 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 baseNotifierque 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
- 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
- Substitua
send()para imprimir:Emailing <recipient>@<domain>: <message> - Substitua
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 - Substitua
send()para imprimir:[URGENT] Emailing <recipient>@<domain>: <message> - Observação: você não pode substituir
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 do e-mail
- Texto da mensagem
Crie dinamicamente três objetos de notificação: um
Notifierbase, umEmailNotifiere umUrgentEmailNotifier, todos usando o mesmo destinatário (e o mesmo domínio quando aplicável). Armazene-os em um array de ponteirosNotifier*.Percorra o array e, para cada notificador, imprima seu tipo usando
getType()e, em seguida, chamesend()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 3pmObserve 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;
}
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 POO
Arquivos externosBuild e compilação em C++Arquivos de cabeçalho e arquivos-fonteNamespaces e escopoIntrodução à POO em C++Classes vs. objetosO ponteiro 'this'Métodos (funções membro)Atributos (membros de dados)Noções básicas de construtores e destruidoresRevisão - calculadora simples4Propriedades de Classe
Membros de Instância vs. EstáticosGetters e SettersFunções Membro ConstPalavra-chave MutableMétodos e Variáveis EstáticosFunções e Classes FriendRevisão - Gerenciador de Contas Bancárias7Herança
Herança básicaNíveis de acesso da herançaOrdem de chamada de construtor e destrutorSobrescrita de métodosFunções virtuais e VTableHerança múltiplaHerança virtualRevisão - Hierarquia de funcionários10Visão geral da STL
Visão geral e filosofia da STLContêineres da STLIteradoresAlgoritmos da STLFunctors e expressões lambdaRecapitulação - Frequência de palavras13Padrões de Projeto — Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonFactory e Abstract FactoryPadrão BuilderPadrão ObserverPadrão Strategy2Gerenciamento de Memória
Memória Stack vs. HeapPonteiros e ReferênciasMemória Dinâmica (new/delete)Ponteiros Inteligentes em C++RAII em C++Revisão - Gerenciador de Array Dinâmico5Encapsulamento
Especificadores de acesso em C++Especificadores de acesso em profundidadeOcultação de informaçõesStruct vs ClasseClasses aninhadas e internasRecapitulação - Sistema de registros de alunos8Polimorfismo
Polimorfismo em compilação vs. execuçãoSobrecarga de funçõesFunções virtuais revisadasFunções virtuais purasClasses abstratasDesign de interfaces em C++Conversão dinâmica e RTTIRecapitulação — Calculadora de formas11Conceitos Avançados de OOP
Composição vs. HerançaMixins via CRTPIdioma PimplApagamento de TiposClasses Enum e Tipagem ForteTratamento de Exceções em OOPHierarquias de Exceções Personalizadas14Padrões de Projeto — Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositeRAII como Padrão3Construtores e Destrutores
Construtor PadrãoConstrutor ParametrizadoConstrutor de CópiaConstrutor de MovimentoListas de Inicialização do ConstrutorConstrutores DelegantesAprofundamento em DestrutoresRegra dos Três / Cinco / ZeroRevisão - Classe String6Sobrecarga de Operadores
Introdução à Sobrecarga de OperadoresSobrecarga de Operadores AritméticosSobrecarga de Operadores de ComparaçãoOperadores de FluxoSobrecarga do Operador de AtribuiçãoSobrecarga dos Operadores [] e ()Operadores de Conversão de TipoRecapitulação - Classe Matrix9Templates
Templates de FunçõesTemplates de ClassesEspecialização de TemplatesTemplates VariádicosNoções Básicas de SFINAE e Traits de TiposRecapitulação - Contêiner GenéricoPratique por conta própria: Compilador de C++ online