Ereditarietà multipla
Fa parte della sezione Programmazione orientata agli oggetti del percorso C++ di Coddy. Lezione 53 di 104.
C++ consente a una classe di ereditare da più classi base contemporaneamente. Questa pratica è chiamata ereditarietà multipla e ti permette di combinare funzionalità di gerarchie di classi diverse.
Per ereditare da più classi, separale con virgole:
class Printer {
public:
void print() {
std::cout << "Printing document" << std::endl;
}
};
class Scanner {
public:
void scan() {
std::cout << "Scanning document" << std::endl;
}
};
class MultiFunctionDevice : public Printer, public Scanner {
public:
void copy() {
scan();
print();
}
};La classe MultiFunctionDevice ora ha accesso ai metodi sia di Printer che di Scanner:
MultiFunctionDevice mfd;
mfd.print(); // Da Printer
mfd.scan(); // Da Scanner
mfd.copy(); // Il suo metodoI costruttori vengono chiamati nell’ordine in cui le classi base sono elencate nella dichiarazione di ereditarietà (da sinistra a destra), mentre i distruttori vengono chiamati in ordine inverso. Puoi passare argomenti a ciascun costruttore base nell’elenco di inizializzazione:
class MultiFunctionDevice : public Printer, public Scanner {
public:
MultiFunctionDevice() : Printer(), Scanner() {}
};Un potenziale problema si presenta quando entrambe le classi base hanno un membro con lo stesso nome. Questo crea ambiguità e devi usare l'operatore di risoluzione dell'ambito per specificare quale intendi:
class A { public: void show() {} };
class B { public: void show() {} };
class C : public A, public B {};
C obj;
obj.A::show(); // Chiama la versione di A
obj.B::show(); // Chiama la versione di BL'ereditarietà multipla è potente, ma può portare a situazioni complesse, soprattutto quando le classi base condividono un antenato comune. Esploreremo questa sfida nella prossima lezione.
Sfida
FacileCostruiamo un sistema di dispositivi per la casa intelligente che combina le funzionalità di più classi base usando l'ereditarietà multipla. Creerai dispositivi che possono connettersi al WiFi ed essere controllati tramite la voce, per poi combinare queste funzionalità in uno smart speaker unificato.
Organizzerai il codice in tre file:
WiFiDevice.h: definisci una classeWiFiDeviceche rappresenta qualsiasi dispositivo in grado di connettersi a una rete:- Un membro protetto
std::string networkName - Un costruttore che accetta il nome di una rete e lo memorizza
- Un metodo pubblico
connect()che stampa:Connecting to WiFi: <networkName> - Un metodo pubblico
status()che stampa:WiFi Status: Connected
- Un membro protetto
VoiceControl.h: definisci una classeVoiceControlche rappresenta qualsiasi dispositivo con funzionalità vocali:- Un membro protetto
std::string assistantName - Un costruttore che accetta il nome di un assistente e lo memorizza
- Un metodo pubblico
listen()che stampa:<assistantName> is listening... - Un metodo pubblico
status()che stampa:Voice Status: Active
- Un membro protetto
main.cpp: leggi tre input (ciascuno su una riga separata):- Nome del dispositivo (stringa)
- Nome della rete WiFi (stringa)
- Nome dell'assistente vocale (stringa)
Definisci una classe
SmartSpeakerche eredita pubblicamente sia daWiFiDevicesia daVoiceControl:- Un membro privato
std::string deviceName - Un costruttore che accetta tutti e tre i parametri, passa il nome della rete a
WiFiDevicee il nome dell'assistente aVoiceControlusando la lista di inizializzazione e memorizza il nome del dispositivo - Un metodo
powerOn()che stampa:<deviceName> powering on..., quindi chiamaconnect()elisten() - Un metodo
showAllStatus()che stampa il nome del dispositivo, quindi usa l'operatore di risoluzione dell'ambito per chiamare entrambi i metodistatus()e risolvere l'ambiguità
Crea un oggetto
SmartSpeakercon i valori di input. ChiamapowerOn(), stampa una riga vuota, quindi chiamashowAllStatus().
Ad esempio, con gli input Echo, HomeNetwork e Alexa:
Echo powering on...
Connecting to WiFi: HomeNetwork
Alexa is listening...
Echo Status:
WiFi Status: Connected
Voice Status: ActiveIl metodo showAllStatus() dovrebbe stampare il nome del dispositivo seguito da " Status:" su una riga, quindi chiamare il metodo status() di ciascuna classe base usando WiFiDevice::status() e VoiceControl::status() per evitare ambiguità, dato che entrambe le classi base hanno un metodo con lo stesso nome.
Provalo tu
#include <iostream>
#include <string>
#include "WiFiDevice.h"
#include "VoiceControl.h"
using namespace std;
// TODO: Definire la classe SmartSpeaker che eredita pubblicamente sia da WiFiDevice sia da VoiceControl
// - Membro privato: deviceName (string)
// - Costruttore: accetta deviceName, networkName, assistantName
// Usare la lista di inizializzazione per passare networkName a WiFiDevice e assistantName a VoiceControl
// - Metodo powerOn(): stampa "<deviceName> powering on...", poi chiama connect() e listen()
// - Metodo showAllStatus(): stampa "<deviceName> Status:", poi chiama entrambi i metodi status()
// usando la risoluzione dell'ambito (WiFiDevice::status() e VoiceControl::status())
class SmartSpeaker {
// TODO: Implementare qui la classe SmartSpeaker
};
int main() {
string deviceName, networkName, assistantName;
getline(cin, deviceName);
getline(cin, networkName);
getline(cin, assistantName);
// TODO: Creare un oggetto SmartSpeaker con i valori di input
// TODO: Chiamare powerOn()
// TODO: Stampare una riga vuota
// TODO: Chiamare showAllStatus()
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