Menu
Coddy logo textTech

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.

challenge icon

Sfida

Facile

Costruiamo 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 comune Employee con:
    • Un std::string name e un int id protetti
    • 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
  • Developer.h: Definisci una classe Developer che usa l’ereditarietà pubblica virtuale da Employee:
    • 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
  • Manager.h: Definisci una classe Manager che usa l’ereditarietà pubblica virtuale da Employee:
    • 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
  • main.cpp: Leggi quattro input (ciascuno su una riga separata):
    1. Nome (stringa)
    2. ID del dipendente (intero)
    3. Linguaggio di programmazione (stringa)
    4. Dimensione del team (intero)

    Definisci una classe TeamLead che eredita pubblicamente sia da Developer sia da Manager:

    • Un costruttore che accetta tutti e quattro i parametri e deve inizializzare direttamente Employee (la base virtuale), poi Developer e Manager
    • Il costruttore dovrebbe stampare: TeamLead [<name>] ready to lead and code!
    • Un metodo showRole() che chiama getInfo(), code() e manage() in quest’ordine
    • Un distruttore che stampa: TeamLead [<name>] promoted out

    Crea un oggetto TeamLead all’interno di un ambito di blocco, chiama showRole(), 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;
}
quiz iconMettiti alla prova

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

Esercitati da solo: Compilatore C++ online