Menu
Coddy logo textTech

Virtuelle Vererbung

Teil des Abschnitts Objektorientierte Programmierung der C++-Journey von Coddy — Lektion 54 von 104.

Das Diamond-Problem tritt auf, wenn eine Klasse von zwei Klassen erbt, die eine gemeinsame Basisklasse teilen. Ohne spezielle Behandlung erhält die abgeleitete Klasse zwei Kopien der gemeinsamen Basis, was zu Mehrdeutigkeit und verschwendetem Speicher führt.

class Animal {
public:
    int age;
};

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

class Bat : public Mammal, public Bird {};

Bat b;
b.age = 5;  // Fehler: mehrdeutig - welches 'age'?

Die Klasse Bat enthält zwei separate Animal-Unterobjekte: eines über Mammal und eines über Bird. Dies erzeugt das rautenförmige Vererbungsdiagramm, das dem Problem seinen Namen gibt.

Virtuelle Vererbung löst dies, indem sichergestellt wird, dass nur eine Kopie der gemeinsamen Basis existiert. Fügen Sie das virtual-Schlüsselwort hinzu, wenn Sie von der gemeinsamen Basis erben:

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;  // Funktioniert! Es existiert nur ein 'age'

Beachten Sie, dass Bat Animal direkt in seinem Konstruktor initialisieren muss. Bei virtueller Vererbung ist die am weitesten abgeleitete Klasse für die Konstruktion der virtuellen Basis verantwortlich, unabhängig von den Zwischenklassen.

challenge icon

Aufgabe

Einfach

Lassen Sie uns ein Mitarbeiterverwaltungssystem erstellen, das demonstriert, wie virtuelle Vererbung das Diamond-Problem löst. Sie werden eine Hierarchie erstellen, in der ein TeamLead sowohl von Developer als auch von Manager erbt, die beide eine gemeinsame Employee-Basisklasse teilen.

Sie werden Ihren Code über vier Dateien organisieren:

  • Employee.h: Definieren Sie die gemeinsame Basisklasse Employee mit:
    • Einem geschützten std::string name und int id
    • Einem Konstruktor, der beide Werte entgegennimmt und ausgibt: Employee [<name>] hired with ID <id>
    • Einer öffentlichen getInfo()-Methode, die ausgibt: Employee: <name> (ID: <id>)
    • Einem virtuellen Destruktor, der ausgibt: Employee [<name>] record closed
  • Developer.h: Definieren Sie eine Developer-Klasse, die virtual public inheritance von Employee verwendet:
    • Einem geschützten std::string language-Member
    • Einem Konstruktor, der Name, ID und Sprache entgegennimmt — Name und ID an Employee übergibt, die Sprache speichert und ausgibt: Developer [<name>] specializes in <language>
    • Einer öffentlichen code()-Methode, die ausgibt: <name> is coding in <language>
    • Einem Destruktor, der ausgibt: Developer [<name>] signed off
  • Manager.h: Definieren Sie eine Manager-Klasse, die virtual public inheritance von Employee verwendet:
    • Einem geschützten int teamSize-Member
    • Einem Konstruktor, der Name, ID und Teamgröße entgegennimmt — Name und ID an Employee übergibt, die Teamgröße speichert und ausgibt: Manager [<name>] leads a team of <teamSize>
    • Einer öffentlichen manage()-Methode, die ausgibt: <name> is managing <teamSize> people
    • Einem Destruktor, der ausgibt: Manager [<name>] stepped down
  • main.cpp: Lesen Sie vier Eingaben ein (jede in einer separaten Zeile):
    1. Name (String)
    2. Mitarbeiter-ID (Ganzzahl)
    3. Programmiersprache (String)
    4. Teamgröße (Ganzzahl)

    Definieren Sie eine TeamLead-Klasse, die öffentlich von sowohl Developer als auch Manager erbt:

    • Einem Konstruktor, der alle vier Parameter entgegennimmt und Employee (die virtuelle Basis) sowie anschließend Developer und Manager direkt initialisieren muss
    • Der Konstruktor sollte ausgeben: TeamLead [<name>] ready to lead and code!
    • Einer showRole()-Methode, die getInfo(), code() und manage() in dieser Reihenfolge aufruft
    • Einem Destruktor, der ausgibt: TeamLead [<name>] promoted out

    Erstellen Sie ein TeamLead-Objekt innerhalb eines Block-Scopes, rufen Sie showRole() auf und lassen Sie es dann den Scope verlassen. Geben Sie nach dem Block aus: Organization restructured!

Zum Beispiel mit den Eingaben Alice, 101, C++ und 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!

Beachten Sie, dass es nur einen Aufruf des Employee-Konstruktors und einen Aufruf des Employee-Destruktors gibt — die virtuelle Vererbung stellt sicher, dass nur eine Kopie der gemeinsamen Basis existiert. Der TeamLead muss Employee direkt initialisieren, da bei virtueller Vererbung die am weitesten abgeleitete Klasse für die Konstruktion der virtuellen Basis verantwortlich ist.

Spickzettel

Das Diamond-Problem tritt auf, wenn eine Klasse von zwei Klassen erbt, die eine gemeinsame Basisklasse teilen, was zu zwei Kopien der Basisklasse führt und Mehrdeutigkeit verursacht.

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: ambiguous - which 'age'?

Virtuelle Vererbung löst das Diamond-Problem, indem sichergestellt wird, dass nur eine Kopie der gemeinsamen Basisklasse existiert. Verwenden Sie das Schlüsselwort virtual bei der Vererbung:

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;  // Works! Only one 'age' exists

Bei virtueller Vererbung muss die am weitesten abgeleitete Klasse die virtuelle Basisklasse direkt in ihrem Konstruktor initialisieren, unabhängig von dazwischenliegenden Klassen.

Probier es selbst

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

using namespace std;

// TODO: Definiere die Klasse TeamLead, die öffentlich von Developer und Manager erbt
// Hinweis: Bei virtueller Vererbung muss TeamLead die Klasse Employee (die virtuelle Basisklasse) direkt initialisieren
class TeamLead : public Developer, public Manager {
public:
    // TODO: Implementiere den Konstruktor, der name, id, language und teamSize entgegennimmt
    // Muss initialisieren: Zuerst Employee (virtuelle Basis), dann Developer, dann Manager
    // Sollte ausgeben: 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: Konstruktor-Nachricht ausgeben
    }

    // TODO: Implementiere die Methode showRole()
    // Sollte getInfo(), code() und manage() in dieser Reihenfolge aufrufen
    void showRole() {
        // TODO: Rufe die drei Methoden auf
    }

    // TODO: Implementiere den Destruktor
    // Sollte ausgeben: TeamLead [<name>] promoted out
    ~TeamLead() {
        // TODO: Destruktor-Nachricht ausgeben
    }
};

int main() {
    // Eingaben lesen
    string name;
    int id;
    string language;
    int teamSize;

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

    // TODO: Erstelle ein TeamLead-Objekt innerhalb eines Block-Scopes
    // Rufe showRole() auf und lasse es dann den Gültigkeitsbereich verlassen
    {
        // TODO: Erstelle TeamLead und rufe showRole() auf
    }

    // Abschlussnachricht nach dem Block ausgeben
    cout << "Organization restructured!" << endl;

    return 0;
}
quiz iconTeste dich selbst

Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.

Alle Lektionen in Objektorientierte Programmierung