Sobrescritura de métodos
Parte de la sección Programación Orientada a Objetos del Journey de C++ de Coddy — lección 51 de 104.
Sobrescritura de métodos ocurre cuando una clase derivada proporciona su propia implementación de un método que ya existe en la clase base. Esto permite que las clases derivadas personalicen el comportamiento heredado manteniendo la misma firma del método.
Para sobrescribir un método, simplemente defina un método en la clase derivada con exactamente el mismo nombre, tipo de retorno y 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(); // Salida: Woof!La versión de la clase derivada oculta la versión de la clase base. Sin embargo, aún puedes acceder al método de la clase base utilizando el operador de resolución de ámbito:
class Dog : public Animal {
public:
void speak() {
Animal::speak(); // Llamar a la versión de la clase base
std::cout << "Woof!" << std::endl;
}
};Existe una limitación importante con esta forma básica de sobrescritura. Cuando accedes a un objeto derivado a través de un puntero o referencia de la clase base, se llama a la versión de la clase base en su lugar:
Dog d;
Animal* ptr = &d;
ptr->speak(); // Salida: Some sound (not Woof!)Esto sucede porque el compilador determina qué método llamar basándose en el tipo de puntero, no en el tipo de objeto real. Para lograr un polimorfismo de tiempo de ejecución real donde se llame al método correcto basándose en el objeto real, necesitarás funciones virtuales, las cuales cubriremos en la próxima lección.
Desafío
FácilVamos a construir un sistema de notificaciones que demuestre la sobrescritura de métodos en acción. Crearás una clase base Notifier y dos notificadores especializados que personalizan cómo se entregan los mensajes.
Organizarás tu código en tres archivos:
Notifier.h: Define una clase baseNotifiercon:- Un miembro protegido
std::string recipient - Un constructor que recibe un nombre de destinatario y lo almacena
- Un método público
send(const std::string& message)que imprime:Sending to <recipient>: <message>
- Un miembro protegido
EmailNotifier.h: Define una claseEmailNotifierque hereda públicamente deNotifier:- Un constructor que recibe un destinatario y lo pasa a la clase base
- Sobrescribe el método
send()para llamar primero a la versión de la clase base usandoNotifier::send(), y luego imprimir:[Email sent successfully]
main.cpp: Lee dos entradas (cada una en una línea separada):- Nombre del destinatario (string)
- Contenido del mensaje (string)
Define una clase
SMSNotifierdirectamente en main.cpp que herede públicamente deNotifier:- Un constructor que recibe un destinatario y lo pasa a la clase base
- Sobrescribe el método
send()para imprimir:SMS to <recipient>: <message>(reemplazando completamente el comportamiento base sin llamarlo)
Crea un objeto de cada tipo de notificador (base
Notifier,EmailNotifier, ySMSNotifier) usando el mismo destinatario. Llama asend()en cada uno con el mensaje de entrada, imprimiendo una línea separadora entre cada uno:--- Base Notifier --- <output> --- Email Notifier --- <output> --- SMS Notifier --- <output>
Por ejemplo, con las entradas Alice y 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!Observa cómo EmailNotifier extiende el comportamiento base llamando primero al método del padre, mientras que SMSNotifier lo reemplaza completamente con su propia implementación. No olvides los protectores de encabezado (header guards) e incluir los encabezados apropiados en cada archivo.
Hoja de referencia
La sobrescritura de métodos (method overriding) permite que una clase derivada proporcione su propia implementación de un método de la clase base, manteniendo la misma firma del método (nombre, tipo de retorno y parámetros).
Sintaxis básica de la sobrescritura:
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!El método de la clase derivada oculta la versión de la clase base. Para llamar al método de la clase base desde la clase derivada, utilice el operador de resolución de ámbito:
class Dog : public Animal {
public:
void speak() {
Animal::speak(); // Call base class version
std::cout << "Woof!" << std::endl;
}
};Limitación importante: Al acceder a un objeto derivado a través de un puntero o referencia de la clase base, se llama a la versión de la clase base:
Dog d;
Animal* ptr = &d;
ptr->speak(); // Output: Some sound (not Woof!)Esto se debe a que el compilador determina qué método llamar basándose en el tipo de puntero, no en el tipo de objeto real. Para un polimorfismo de tiempo de ejecución real, utilice funciones virtuales.
Pruébalo tú mismo
#include <iostream>
#include <string>
#include "Notifier.h"
#include "EmailNotifier.h"
using namespace std;
// TODO: Define la clase SMSNotifier aquí que herede públicamente de Notifier
// - Constructor que pasa recipient a la clase base
// - Sobrescribe send() para imprimir: SMS to <recipient>: <message>
// (reemplaza completamente el comportamiento base, no llames al send() del padre)
int main() {
string recipient;
string message;
getline(cin, recipient);
getline(cin, message);
// TODO: Crea un objeto Notifier con recipient
// Imprime "--- Base Notifier ---"
// Llama a send() con message
// TODO: Crea un objeto EmailNotifier con recipient
// Imprime "--- Email Notifier ---"
// Llama a send() con message
// TODO: Crea un objeto SMSNotifier con recipient
// Imprime "--- SMS Notifier ---"
// Llama a send() con message
return 0;
}
Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.
Todas las lecciones de Programación Orientada a Objetos
1Fundamentos de OOP
Archivos externosConstrucción y compilación en C++Archivos de cabecera y archivos fuenteNamespaces y alcanceIntroducción a OOP en C++Clases vs ObjetosEl puntero 'this'Métodos (Funciones miembro)Atributos (Miembros de datos)Conceptos básicos de Ctors y DtorsResumen - Calculadora simple4Propiedades de clase
Miembros de instancia vs. estáticosGetters y SettersFunciones miembro constPalabra clave mutableMétodos y variables estáticosFunciones y clases amigasResumen - Gestor de cuentas bancarias7Herencia
Herencia básicaNiveles de acceso en la herenciaOrden de llamada de Ctor y DtorSobrescritura de métodosFunciones virtuales y VTableHerencia múltipleHerencia virtualResumen - Jerarquía de empleados2Gestión de memoria
Memoria Stack vs HeapPunteros y referenciasMemoria dinámica (new/delete)Punteros inteligentes en C++RAII en C++Resumen - Gestor de arrays dinámicos5Encapsulamiento
Especificadores de acceso en C++Especificadores de acceso en profundidadOcultamiento de informaciónStruct vs ClassClases anidadas e internasResumen - Sistema de registros de estudiantes8Polimorfismo
Polimorfismo: Compilación vs. Tiempo de ejecuciónSobrecarga de funcionesFunciones virtuales revisadasFunciones virtuales purasClases abstractasDiseño de interfaces en C++Dynamic Casting y RTTIResumen: Calculadora de figuras11Conceptos avanzados de POO
Composición vs. HerenciaMixins mediante CRTPIdioma PimplBorrado de tiposEnum Classes y tipado fuerteManejo de excepciones en POOJerarquías de excepciones personalizadas14Patrones de diseño - Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositeRAII como patrón3Constructores y Destructores
Constructor por defectoConstructor parametrizadoConstructor de copiaConstructor de movimientoListas de inicialización del constructorConstructores delegadosAnálisis profundo del destructorRegla de tres / cinco / ceroResumen - Clase String6Sobrecarga de operadores
Introducción a la sobrecarga de operadoresSobrecarga de operadores aritméticosSobrecarga de operadores de comparaciónOperadores de flujo (Stream)Sobrecarga del operador de asignaciónSobrecarga de los operadores [] y ()Operadores de conversión de tiposResumen - Clase Matrix9Plantillas
Plantillas de funcionesPlantillas de clasesEspecialización de plantillasPlantillas variádicasConceptos básicos de SFINAE y Type TraitsResumen - Contenedor genérico