Ereditarietà virtuale
Fa parte della sezione Programmazione orientata agli oggetti del percorso C++ di Coddy. Lezione 54 di 104.
Il problema del diamante si verifica quando una classe eredita da due classi che condividono una classe base comune. Senza una gestione speciale, la classe derivata finisce per avere due copie della classe base comune, causando ambiguità e spreco di memoria.
class Animal {
public:
int age;
};
class Mammal : public Animal {};
class Bird : public Animal {};
class Bat : public Mammal, public Bird {};
Bat b;
b.age = 5; // Error: ambiguo - ¿cuál 'age'?La classe Bat contiene due sottoggetti separati di Animal: uno tramite Mammal e uno tramite Bird. Questo crea il diagramma di ereditarietà a forma di diamante che dà il nome al problema.
L’ereditarietà virtuale risolve questo problema garantendo che esista una sola copia della base comune. Aggiungi la parola chiave virtual quando erediti dalla base condivisa:
class Animal {
public:
int age;
Animal(int a = 0) : age(a) {}
};
class Mammal : virtual public Animal {
public:
Mammal(int a = 0) : Animal(a) {}
};
class Bird : virtual public Animal {
public:
Bird(int a = 0) : Animal(a) {}
};
class Bat : public Mammal, public Bird {
public:
Bat(int a) : Animal(a), Mammal(a), Bird(a) {}
};
Bat b(5);
b.age = 10; // Funziona! Esiste un solo 'age'Nota che Bat deve inizializzare direttamente Animal nel suo costruttore. Con l'ereditarietà virtuale, la classe più derivata è responsabile della costruzione della classe base virtuale, indipendentemente dalle classi intermedie.
Sfida
FacileCostruiamo un sistema di gestione del personale che mostri come l’ereditarietà virtuale risolve il problema del diamante. Creerai una gerarchia in cui TeamLead eredita sia da Developer sia da Manager, che condividono entrambi una classe base comune Employee.
Organizzerai il codice in quattro file:
Employee.h: Definisci la classe base comuneEmployeecon:- Un
std::string namee unint idprotetti - Un costruttore che accetta entrambi i valori e stampa:
Employee [<name>] hired with ID <id> - Un metodo pubblico
getInfo()che stampa:Employee: <name> (ID: <id>) - Un distruttore virtuale che stampa:
Employee [<name>] record closed
- Un
Developer.h: Definisci una classeDeveloperche usa l’ereditarietà pubblica virtuale daEmployee:- Un membro protetto
std::string language - Un costruttore che accetta name, id e language: passa name e id a
Employee, memorizza language e stampa:Developer [<name>] specializes in <language> - Un metodo pubblico
code()che stampa:<name> is coding in <language> - Un distruttore che stampa:
Developer [<name>] signed off
- Un membro protetto
Manager.h: Definisci una classeManagerche usa l’ereditarietà pubblica virtuale daEmployee:- Un membro protetto
int teamSize - Un costruttore che accetta name, id e team size: passa name e id a
Employee, memorizza team size e stampa:Manager [<name>] leads a team of <teamSize> - Un metodo pubblico
manage()che stampa:<name> is managing <teamSize> people - Un distruttore che stampa:
Manager [<name>] stepped down
- Un membro protetto
main.cpp: Leggi quattro input (ciascuno su una riga separata):- Nome (stringa)
- ID del dipendente (intero)
- Linguaggio di programmazione (stringa)
- Dimensione del team (intero)
Definisci una classe
TeamLeadche eredita pubblicamente sia daDevelopersia daManager:- Un costruttore che accetta tutti e quattro i parametri e deve inizializzare direttamente
Employee(la base virtuale), poiDevelopereManager - Il costruttore dovrebbe stampare:
TeamLead [<name>] ready to lead and code! - Un metodo
showRole()che chiamagetInfo(),code()emanage()in quest’ordine - Un distruttore che stampa:
TeamLead [<name>] promoted out
Crea un oggetto
TeamLeadall’interno di un ambito di blocco, chiamashowRole(), poi lascia che esca dall’ambito. Dopo il blocco, stampa:Organization restructured!
Per esempio, con gli input Alice, 101, C++ e 5:
Employee [Alice] hired with ID 101
Developer [Alice] specializes in C++
Manager [Alice] leads a team of 5
TeamLead [Alice] ready to lead and code!
Employee: Alice (ID: 101)
Alice is coding in C++
Alice is managing 5 people
TeamLead [Alice] promoted out
Manager [Alice] stepped down
Developer [Alice] signed off
Employee [Alice] record closed
Organization restructured!Nota che viene chiamato un solo costruttore di Employee e un solo distruttore di Employee: l’ereditarietà virtuale garantisce che esista una sola copia della base condivisa. TeamLead deve inizializzare direttamente Employee perché, con l’ereditarietà virtuale, la classe più derivata è responsabile della costruzione della base virtuale.
Provalo tu
#include <iostream>
#include <string>
#include "Developer.h"
#include "Manager.h"
using namespace std;
// TODO: Definisci la classe TeamLead che eredita pubblicamente sia da Developer sia da Manager
// Ricorda: con l'ereditarietà virtuale, TeamLead deve inizializzare direttamente Employee (la classe base virtuale)
class TeamLead : public Developer, public Manager {
public:
// TODO: Implementa un costruttore che accetta name, id, language e teamSize
// Deve inizializzare: prima Employee (classe base virtuale), poi Developer, infine Manager
// Deve stampare: TeamLead [<name>] ready to lead and code!
TeamLead(const std::string& name, int id, const std::string& language, int teamSize)
: Employee(name, id),
Developer(name, id, language),
Manager(name, id, teamSize) {
// TODO: Stampa il messaggio del costruttore
}
// TODO: Implementa il metodo showRole()
// Deve chiamare getInfo(), code() e manage() in quest'ordine
void showRole() {
// TODO: Chiama i tre metodi
}
// TODO: Implementa il distruttore
// Deve stampare: TeamLead [<name>] promoted out
~TeamLead() {
// TODO: Stampa il messaggio del distruttore
}
};
int main() {
// Leggi gli input
string name;
int id;
string language;
int teamSize;
getline(cin, name);
cin >> id;
cin.ignore();
getline(cin, language);
cin >> teamSize;
// TODO: Crea un oggetto TeamLead nell'ambito di un blocco
// Chiama showRole(), poi lascia che l'oggetto esca dall'ambito
{
// TODO: Crea TeamLead e chiama showRole()
}
// Stampa il messaggio finale dopo il blocco
cout << "Organization restructured!" << endl;
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