Sobrescrita de Métodos
Parte da seção Programação Orientada a Objetos do Journey de C++ da Coddy — lição 51 de 104.
Sobrescrita de método ocorre quando uma classe derivada fornece sua própria implementação de um método que já existe na classe base. Isso permite que as classes derivadas personalizem o comportamento herdado, mantendo a mesma assinatura de método.
Para sobrescrever um método, basta definir um método na classe derivada com exatamente o mesmo nome, tipo de retorno e parâmetros:
class Animal {
public:
void speak() {
std::cout << "Some sound" << std::endl;
}
};
class Dog : public Animal {
public:
void speak() {
std::cout << "Woof!" << std::endl;
}
};
Dog d;
d.speak(); // Saída: Woof!A versão da classe derivada oculta a versão da classe base. No entanto, você ainda pode acessar o método da classe base usando o operador de resolução de escopo:
class Dog : public Animal {
public:
void speak() {
Animal::speak(); // Chama a versão da classe base
std::cout << "Woof!" << std::endl;
}
};Existe uma limitação importante com esta forma básica de sobrescrita. Quando você acessa um objeto derivado através de um ponteiro ou referência da classe base, a versão da classe base é chamada em vez disso:
Dog d;
Animal* ptr = &d;
ptr->speak(); // Saída: Some sound (não Woof!)Isso acontece porque o compilador determina qual método chamar com base no tipo do ponteiro, não no tipo real do objeto. Para alcançar o verdadeiro polimorfismo de tempo de execução, onde o método correto é chamado com base no objeto real, você precisará de funções virtuais, que abordaremos na próxima lição.
Desafio
FácilVamos construir um sistema de notificação que demonstra a sobrescrita de métodos (method overriding) em ação. Você criará uma classe base Notifier e dois notificadores especializados que personalizam como as mensagens são entregues.
Você organizará seu código em três arquivos:
Notifier.h: Defina uma classe baseNotifiercom:- Um membro protegido
std::string recipient - Um construtor que recebe o nome de um destinatário e o armazena
- Um método público
send(const std::string& message)que imprime:Sending to <recipient>: <message>
- Um membro protegido
EmailNotifier.h: Defina uma classeEmailNotifierque herda publicamente deNotifier:- Um construtor que recebe um destinatário e o passa para a classe base
- Sobrescreva o método
send()para primeiro chamar a versão da classe base usandoNotifier::send(), e depois imprimir:[Email sent successfully]
main.cpp: Leia duas entradas (cada uma em uma linha separada):- Nome do destinatário (string)
- Conteúdo da mensagem (string)
Defina uma classe
SMSNotifierdiretamente no main.cpp que herda publicamente deNotifier:- Um construtor que recebe um destinatário e o passa para a classe base
- Sobrescreva o método
send()para imprimir:SMS to <recipient>: <message>(substituindo completamente o comportamento base sem chamá-lo)
Crie um objeto de cada tipo de notificador (base
Notifier,EmailNotifiereSMSNotifier) usando o mesmo destinatário. Chamesend()em cada um com a mensagem de entrada, imprimindo uma linha separadora entre cada um:--- Base Notifier --- <output> --- Email Notifier --- <output> --- SMS Notifier --- <output>
Por exemplo, com as entradas Alice e Hello there!:
--- Base Notifier ---
Sending to Alice: Hello there!
--- Email Notifier ---
Sending to Alice: Hello there!
[Email sent successfully]
--- SMS Notifier ---
SMS to Alice: Hello there!Observe como o EmailNotifier estende o comportamento base chamando o método pai primeiro, enquanto o SMSNotifier o substitui completamente por sua própria implementação. Não se esqueça dos header guards e de incluir os cabeçalhos apropriados em cada arquivo.
Folha de consulta
A sobrescrita de método (method overriding) permite que uma classe derivada forneça sua própria implementação de um método da classe base, mantendo a mesma assinatura do método (nome, tipo de retorno e parâmetros).
Sintaxe básica de sobrescrita:
class Animal {
public:
void speak() {
std::cout << "Some sound" << std::endl;
}
};
class Dog : public Animal {
public:
void speak() {
std::cout << "Woof!" << std::endl;
}
};
Dog d;
d.speak(); // Output: Woof!O método da classe derivada oculta a versão da classe base. Para chamar o método da classe base a partir da classe derivada, use o operador de resolução de escopo:
class Dog : public Animal {
public:
void speak() {
Animal::speak(); // Call base class version
std::cout << "Woof!" << std::endl;
}
};Limitação importante: Ao acessar um objeto derivado por meio de um ponteiro ou referência da classe base, a versão da classe base é chamada:
Dog d;
Animal* ptr = &d;
ptr->speak(); // Output: Some sound (not Woof!)Isso ocorre porque o compilador determina qual método chamar com base no tipo do ponteiro, não no tipo real do objeto. Para um polimorfismo de tempo de execução real, use funções virtuais.
Experimente você mesmo
#include <iostream>
#include <string>
#include "Notifier.h"
#include "EmailNotifier.h"
using namespace std;
// TODO: Defina a classe SMSNotifier aqui que herda publicamente de Notifier
// - Construtor que passa recipient para a classe base
// - Sobrescreva send() para imprimir: SMS to <recipient>: <message>
// (substitua completamente o comportamento base, não chame o send() pai)
int main() {
string recipient;
string message;
getline(cin, recipient);
getline(cin, message);
// TODO: Crie um objeto Notifier com recipient
// Imprima "--- Base Notifier ---"
// Chame send() com message
// TODO: Crie um objeto EmailNotifier com recipient
// Imprima "--- Email Notifier ---"
// Chame send() com message
// TODO: Crie um objeto SMSNotifier com recipient
// Imprima "--- SMS Notifier ---"
// Chame send() com message
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