Menu
Coddy logo textTech

Dziedziczenie wirtualne

Część sekcji Programowanie obiektowe ścieżki C++ w Coddy. Lekcja 54 z 104.

Problem diamentu występuje, gdy klasa dziedziczy po dwóch klasach, które mają wspólną klasę bazową. Bez specjalnej obsługi klasa pochodna ma dwie kopie wspólnej klasy bazowej, co powoduje niejednoznaczność i marnowanie pamięci.

class Animal {
public:
    int age;
};

class Mammal : public Animal {};
class Bird : public Animal {};

class Bat : public Mammal, public Bird {};

Bat b;
b.age = 5;  // Błąd: niejednoznaczność – które 'age'?

Klasa Bat zawiera dwa oddzielne podobiekty Animal: jeden za pośrednictwem Mammal, a drugi za pośrednictwem Bird. Tworzy to diagram dziedziczenia w kształcie rombu, od którego problem wziął swoją nazwę.

Dziedziczenie wirtualne rozwiązuje ten problem, zapewniając, że istnieje tylko jedna kopia wspólnej klasy bazowej. Dodaj słowo kluczowe virtual podczas dziedziczenia po wspólnej klasie bazowej:

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;  // Działa! Istnieje tylko jedno 'age'

Zauważ, że Bat musi bezpośrednio zainicjalizować Animal w swoim konstruktorze. W przypadku dziedziczenia wirtualnego za konstruowanie wirtualnej klasy bazowej odpowiada klasa najbardziej pochodna, niezależnie od klas pośrednich.

challenge icon

Wyzwanie

Łatwy

Zbudujmy system zarządzania pracownikami, który pokazuje, jak dziedziczenie wirtualne rozwiązuje problem diamentu. Utworzysz hierarchię, w której TeamLead dziedziczy zarówno po Developer, jak i po Manager, które mają wspólną klasę bazową Employee.

Rozmieścisz kod w czterech plikach:

  • Employee.h: Zdefiniuj wspólną klasę bazową Employee, która zawiera:
    • Chronione składowe std::string name oraz int id
    • Konstruktor przyjmujący obie wartości i wypisujący: Employee [<name>] hired with ID <id>
    • Publiczną metodę getInfo(), która wypisuje: Employee: <name> (ID: <id>)
    • Wirtualny destruktor, który wypisuje: Employee [<name>] record closed
  • Developer.h: Zdefiniuj klasę Developer, która korzysta z wirtualnego dziedziczenia publicznego po Employee:
    • Chronione składowe std::string language
    • Konstruktor przyjmujący imię i nazwisko, identyfikator oraz język: przekazuje imię i nazwisko oraz identyfikator do Employee, zapisuje język i wypisuje: Developer [<name>] specializes in <language>
    • Publiczną metodę code(), która wypisuje: <name> is coding in <language>
    • Destruktor, który wypisuje: Developer [<name>] signed off
  • Manager.h: Zdefiniuj klasę Manager, która korzysta z wirtualnego dziedziczenia publicznego po Employee:
    • Chronione składowe int teamSize
    • Konstruktor przyjmujący imię i nazwisko, identyfikator oraz rozmiar zespołu: przekazuje imię i nazwisko oraz identyfikator do Employee, zapisuje rozmiar zespołu i wypisuje: Manager [<name>] leads a team of <teamSize>
    • Publiczną metodę manage(), która wypisuje: <name> is managing <teamSize> people
    • Destruktor, który wypisuje: Manager [<name>] stepped down
  • main.cpp: Odczytaj cztery dane wejściowe (każdą w osobnym wierszu):
    1. Imię i nazwisko (ciąg znaków)
    2. Identyfikator pracownika (liczba całkowita)
    3. Język programowania (ciąg znaków)
    4. Rozmiar zespołu (liczba całkowita)

    Zdefiniuj klasę TeamLead, która publicznie dziedziczy zarówno po Developer, jak i po Manager:

    • Konstruktor przyjmujący wszystkie cztery parametry, który musi bezpośrednio zainicjalizować Employee (bazę wirtualną), a następnie Developer i Manager
    • Konstruktor powinien wypisać: TeamLead [<name>] ready to lead and code!
    • Metodę showRole(), która wywołuje kolejno getInfo(), code() i manage()
    • Destruktor, który wypisuje: TeamLead [<name>] promoted out

    Utwórz obiekt TeamLead w zakresie bloku, wywołaj showRole(), a następnie pozwól, by obiekt wyszedł poza zakres. Po bloku wypisz: Organization restructured!

Na przykład dla danych wejściowych Alice, 101, C++ i 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!

Zwróć uwagę, że wywołanie konstruktora Employee i wywołanie destruktora Employee występują tylko raz: dziedziczenie wirtualne gwarantuje, że istnieje tylko jedna kopia wspólnej klasy bazowej. Klasa TeamLead musi bezpośrednio inicjalizować Employee, ponieważ przy dziedziczeniu wirtualnym za konstruowanie bazy wirtualnej odpowiada klasa najbardziej pochodna.

Spróbuj swoich sił

#include <iostream>
#include <string>
#include "Developer.h"
#include "Manager.h"

using namespace std;

// TODO: Zdefiniuj klasę TeamLead, która publicznie dziedziczy po Developer i Manager
// Pamiętaj: Przy dziedziczeniu wirtualnym TeamLead musi bezpośrednio inicjalizować Employee (wirtualną klasę bazową)
class TeamLead : public Developer, public Manager {
public:
    // TODO: Zaimplementuj konstruktor przyjmujący name, id, language i teamSize
    // Należy zainicjalizować: najpierw Employee (wirtualną klasę bazową), potem Developer, a następnie Manager
    // Powinno wypisać: 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: Wypisz komunikat konstruktora
    }

    // TODO: Zaimplementuj metodę showRole()
    // Metoda powinna wywołać getInfo(), code() i manage() w tej kolejności
    void showRole() {
        // TODO: Wywołaj te trzy metody
    }

    // TODO: Zaimplementuj destruktor
    // Powinno wypisać: TeamLead [<name>] promoted out
    ~TeamLead() {
        // TODO: Wypisz komunikat destruktora
    }
};

int main() {
    // Wczytaj dane wejściowe
    string name;
    int id;
    string language;
    int teamSize;

    getline(cin, name);
    cin >> id;
    cin.ignore();
    getline(cin, language);
    cin >> teamSize;

    // TODO: Utwórz obiekt TeamLead w zakresie bloku
    // Wywołaj showRole(), a następnie pozwól obiektowi wyjść poza zakres
    {
        // TODO: Utwórz obiekt TeamLead i wywołaj showRole()
    }

    // Wypisz końcowy komunikat po zakończeniu bloku
    cout << "Organization restructured!" << endl;

    return 0;
}
quiz iconSprawdź się

Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.

Wszystkie lekcje w sekcji Programowanie obiektowe

Poćwicz samodzielnie: Kompilator C++ online