Ridefinizione dei metodi
Fa parte della sezione Programmazione orientata agli oggetti del percorso C++ di Coddy. Lezione 51 di 104.
L'override dei metodi si verifica quando una classe derivata fornisce una propria implementazione di un metodo già presente nella classe base. Questo permette alle classi derivate di personalizzare il comportamento ereditato mantenendo la stessa firma del metodo.
Per sovrascrivere un metodo, basta definire un metodo nella classe derivata con lo stesso identico nome, tipo restituito e parametri:
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(); // Risultato: Woof!La versione della classe derivata nasconde quella della classe base. Tuttavia, puoi comunque accedere al metodo della classe base usando l'operatore di risoluzione dell'ambito:
class Dog : public Animal {
public:
void speak() {
Animal::speak(); // Chiama la versione della classe base
std::cout << "Woof!" << std::endl;
}
};C'è una limitazione importante in questa forma di base dell'override. Quando accedi a un oggetto derivato tramite un puntatore o un riferimento alla classe base, viene chiamata la versione della classe base:
Dog d;
Animal* ptr = &d;
ptr->speak(); // Output: Un suono (non Woof!)Questo accade perché il compilatore determina quale metodo chiamare in base al tipo del puntatore, non al tipo effettivo dell’oggetto. Per ottenere un vero polimorfismo a runtime, in cui il metodo corretto viene chiamato in base all’oggetto effettivo, ti serviranno le funzioni virtuali, che vedremo nella prossima lezione.
Sfida
FacileCostruiamo un sistema di notifiche che mostri la sovrascrittura dei metodi in azione. Creerai una classe base Notifier e due notificatori specializzati che personalizzano il modo in cui vengono recapitati i messaggi.
Organizzerai il codice in tre file:
Notifier.h: definisci una classe baseNotifiercon:- Un membro protetto
std::string recipient - Un costruttore che accetta il nome di un destinatario e lo memorizza
- Un metodo pubblico
send(const std::string& message)che stampa:Sending to <recipient>: <message>
- Un membro protetto
EmailNotifier.h: definisci una classeEmailNotifierche eredita pubblicamente daNotifier:- Un costruttore che accetta un destinatario e lo passa alla classe base
- Sovrascrivi il metodo
send()in modo che chiami prima la versione della classe base usandoNotifier::send(), quindi stampi:[Email sent successfully]
main.cpp: leggi due input (ciascuno su una riga separata):- Nome del destinatario (stringa)
- Contenuto del messaggio (stringa)
Definisci direttamente in main.cpp una classe
SMSNotifierche eredita pubblicamente daNotifier:- Un costruttore che accetta un destinatario e lo passa alla classe base
- Sovrascrivi il metodo
send()in modo che stampi:SMS to <recipient>: <message>(sostituendo completamente il comportamento della classe base senza chiamarlo)
Crea un oggetto per ciascun tipo di notificatore (base
Notifier,EmailNotifiereSMSNotifier) usando lo stesso destinatario. Chiamasend()su ciascuno con il messaggio di input e stampa una riga di separazione tra uno e l'altro:--- Base Notifier --- <output> --- Email Notifier --- <output> --- SMS Notifier --- <output>
Per esempio, con gli input 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!Nota come EmailNotifier estenda il comportamento della classe base chiamando prima il metodo del genitore, mentre SMSNotifier lo sostituisca completamente con la propria implementazione. Non dimenticare le protezioni delle intestazioni e di includere le intestazioni appropriate in ciascun file.
Provalo tu
#include <iostream>
#include <string>
#include "Notifier.h"
#include "EmailNotifier.h"
using namespace std;
// TODO: Definisci qui la classe SMSNotifier che eredita pubblicamente da Notifier
// - Costruttore che passa recipient alla classe base
// - Ridefinisci send() per stampare: SMS to <recipient>: <message>
// (sostituisci completamente il comportamento della classe base, senza chiamare send() della classe genitore)
int main() {
string recipient;
string message;
getline(cin, recipient);
getline(cin, message);
// TODO: Crea un oggetto Notifier con recipient
// Stampa "--- Base Notifier ---"
// Chiama send() con message
// TODO: Crea un oggetto EmailNotifier con recipient
// Stampa "--- Email Notifier ---"
// Chiama send() con message
// TODO: Crea un oggetto SMSNotifier con recipient
// Stampa "--- SMS Notifier ---"
// Chiama send() con message
return 0;
}
Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Programmazione orientata agli oggetti
1Fondamenti della programmazione orientata agli oggetti
File esterniBuild e compilazione in C++File header e file sorgenteNamespace e ambitoIntroduzione alla programmazione orientata agli oggetti in C++Classi e oggetti a confrontoIl puntatore 'this'Metodi (funzioni membro)Attributi (membri dati)Fondamenti di costruttori e distruttoriRiepilogo - Calcolatrice semplice4Proprietà delle classi
Membri di istanza e staticiGetter e setterFunzioni membro constParola chiave mutableMetodi e variabili staticiFunzioni e classi friendRiepilogo - Gestore di conti bancari7Ereditarietà
Ereditarietà di baseLivelli di accesso nell’ereditarietàOrdine di chiamata di costruttori e distruttoriRidefinizione dei metodiFunzioni virtuali e VTableEreditarietà multiplaEreditarietà virtualeRiepilogo - Gerarchia dei dipendenti2Gestione della memoria
Memoria Stack vs HeapPuntatori e riferimentiMemoria dinamica (new/delete)Puntatori intelligenti in C++RAII in C++Riepilogo - Gestore di array dinamico5Incapsulamento
Specificatori di accesso in C++Specificatori di accesso in dettaglioOccultamento delle informazioniStruct vs classClassi annidate e interneRiepilogo - Sistema di registrazione degli studenti8Polimorfismo
Polimorfismo a compile time e a runtimeOverload delle funzioniFunzioni virtuali: ripassoFunzioni virtuali pureClassi astratteProgettazione delle interfacce in C++Dynamic casting e RTTIRipasso: calcolatrice di forme11Concetti avanzati di OOP
Composizione vs ereditarietàMixin tramite CRTPIdiom PimplType ErasureEnum class e tipizzazione forteGestione delle eccezioni in OOPGerarchie personalizzate di eccezioni14Pattern di progettazione - Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositeRAII come pattern3Costruttori e distruttori
Costruttore predefinitoCostruttore con parametriCostruttore di copiaCostruttore di spostamentoListe di inizializzazione dei costruttoriCostruttori delegantiApprofondimento sui distruttoriRegola del Tre / Cinque / ZeroRipasso - classe String6Sovraccarico degli operatori
Introduzione al sovraccarico degli operatoriSovraccarico degli operatori aritmeticiSovraccarico degli operatori di confrontoOperatori di flussoSovraccarico dell'operatore di assegnazioneSovraccarico degli operatori [] e ()Operatori di conversione di tipoRipasso - Classe Matrix9Template
Template di funzioneTemplate di classeSpecializzazione dei templateTemplate variadiciBasi di SFINAE e dei trait di tipoRiepilogo - Contenitore genericoEsercitati da solo: Compilatore C++ online