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 haben. Ohne spezielle Behandlung enthält die abgeleitete Klasse letztendlich zwei Kopien der gemeinsamen Basisklasse, was zu Mehrdeutigkeiten und Speichervergeudung 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. Dadurch entsteht das rautenförmige Vererbungsdiagramm, das dem Problem seinen Namen gibt.
Virtuelle Vererbung löst dieses Problem, indem sie sicherstellt, dass nur eine Kopie der gemeinsamen Basisklasse vorhanden ist. Füge beim Erben von der gemeinsamen Basisklasse das Schlüsselwort virtual hinzu:
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'Beachte, dass Bat Animal in seinem constructor direkt initialize muss. Bei virtueller Vererbung ist die am stärksten abgeleitete Klasse unabhängig von den Zwischenklassen für die Konstruktion der virtuellen Basisklasse verantwortlich.
Aufgabe
EinfachErstellen wir ein Mitarbeiterverwaltungssystem, das zeigt, wie virtuelle Vererbung das Diamantproblem löst. Du erstellst eine Hierarchie, in der TeamLead sowohl von Developer als auch von Manager erbt, die beide eine gemeinsame Basisklasse Employee haben.
Du organisierst deinen Code auf vier Dateien verteilt:
Employee.h: Definiere die gemeinsame BasisklasseEmployeemit:- einem geschützten
std::string nameundint id - einem Konstruktor, der beide Werte übernimmt und Folgendes ausgibt:
Employee [<name>] hired with ID <id> - einer öffentlichen Methode
getInfo(), die Folgendes ausgibt:Employee: <name> (ID: <id>) - einem virtuellen Destruktor, der Folgendes ausgibt:
Employee [<name>] record closed
- einem geschützten
Developer.h: Definiere eine KlasseDeveloper, die die virtuelle öffentliche Vererbung vonEmployeeverwendet:- ein geschütztes Element
std::string language - einen Konstruktor, der Name, ID und Sprache übernimmt: Er übergibt Name und ID an
Employee, speichert die Sprache und gibt Folgendes aus:Developer [<name>] specializes in <language> - eine öffentliche Methode
code(), die Folgendes ausgibt:<name> is coding in <language> - einen Destruktor, der Folgendes ausgibt:
Developer [<name>] signed off
- ein geschütztes Element
Manager.h: Definiere eine KlasseManager, die die virtuelle öffentliche Vererbung vonEmployeeverwendet:- ein geschütztes Element
int teamSize - einen Konstruktor, der Name, ID und Teamgröße übernimmt: Er übergibt Name und ID an
Employee, speichert die Teamgröße und gibt Folgendes aus:Manager [<name>] leads a team of <teamSize> - eine öffentliche Methode
manage(), die Folgendes ausgibt:<name> is managing <teamSize> people - einen Destruktor, der Folgendes ausgibt:
Manager [<name>] stepped down
- ein geschütztes Element
main.cpp: Lies vier Eingaben ein, jeweils in einer separaten Zeile:- Name (String)
- Mitarbeiter-ID (Ganzzahl)
- Programmiersprache (String)
- Teamgröße (Ganzzahl)
Definiere eine Klasse
TeamLead, die öffentlich sowohl vonDeveloperals auch vonManagererbt:- einen Konstruktor, der alle vier Parameter übernimmt und
Employee(die virtuelle Basisklasse) direkt initialisieren muss, anschließendDeveloperundManager - Der Konstruktor sollte Folgendes ausgeben:
TeamLead [<name>] ready to lead and code! - eine Methode
showRole(), diegetInfo(),code()undmanage()in dieser Reihenfolge aufruft - einen Destruktor, der Folgendes ausgibt:
TeamLead [<name>] promoted out
Erstelle innerhalb eines Blockbereichs ein
TeamLead-Objekt, rufeshowRole()auf und lasse es anschließend den Gültigkeitsbereich verlassen. Gib nach dem Block Folgendes 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!Beachte, 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 Basisklasse existiert. TeamLead muss Employee direkt initialisieren, da bei virtueller Vererbung die am weitesten abgeleitete Klasse für die Konstruktion der virtuellen Basisklasse verantwortlich ist.
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
// Merke: Bei virtueller Vererbung muss TeamLead 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 Basisklasse), dann Developer, dann Manager
// Should print: 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
// Should print: 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, dann lass es den Gültigkeitsbereich verlassen
{
// TODO: Erstelle TeamLead und rufe showRole() auf
}
// Finale Nachricht nach dem Block ausgeben
cout << "Organization restructured!" << endl;
return 0;
}
Diese Lektion enthält ein kurzes Quiz. Starte die Lektion, um es zu beantworten und deinen Fortschritt zu speichern.
Alle Lektionen in Objektorientierte Programmierung
1Grundlagen der OOP
Externe DateienC++ Build & KompilierungHeader-Dateien & QuelldateienNamespaces & GültigkeitsbereichEinführung in OOP mit C++Klassen vs. ObjekteDer „this“-ZeigerMethoden (Member-Funktionen)Attribute (Datenmember)Grundlagen zu Ctors & DtorsRückblick – Einfacher Taschenrechner4Klassenattribute
Instanz- vs. statische MemberGetter und SetterConst-MemberfunktionenMutable-SchlüsselwortStatische Methoden und VariablenFriend-Funktionen und -KlassenZusammenfassung – Bankkontoverwaltung7Vererbung
Grundlagen der VererbungZugriffsebenen bei der VererbungAufrufreihenfolge von Ctor & DtorÜberschreiben von MethodenVirtuelle Funktionen & VTableMehrfachvererbungVirtuelle VererbungRückblick – Mitarbeiterhierarchie2Speicherverwaltung
Stack- vs. Heap-SpeicherZeiger und ReferenzenDynamischer Speicher (new/delete)Intelligente Zeiger in C++RAII in C++Rückblick – Dynamischer Array-Manager5Kapselung
Zugriffsbezeichner in C++Zugriffsbezeichner im DetailInformationsverbergungStruct vs. KlasseVerschachtelte und innere KlassenWiederholung – Studierendenverwaltungssystem8Polymorphie
Polymorphie zur Compilezeit vs. LaufzeitFunktionsüberladungVirtuelle Funktionen erneut betrachtetRein virtuelle FunktionenAbstrakte KlassenInterface-Design in C++Dynamic Casting & RTTIRückblick – Shape-Calculator3Konstruktoren & Destruktoren
StandardkonstruktorParametrisierter KonstruktorKopierkonstruktorMove-KonstruktorKonstruktor-InitialisierungslistenDelegierende KonstruktorenDestruktor im DetailDrei-/Fünf-/Null-RegelZusammenfassung – String-Klasse6Operatorüberladung
Einführung in die OperatorüberladungArithmetische OperatorüberladungVergleichsoperatorüberladungStream-OperatorenZuweisungsoperatorüberladungÜberladung der Operatoren [] und ()TypkonvertierungsoperatorenWiederholung – Matrix-Klasse9Templates
FunktionstemplatesKlassen-TemplatesTemplate-SpezialisierungVariadische TemplatesGrundlagen zu SFINAE und Type TraitsRückblick – Generischer ContainerÜbe selbstständig: Online-C++-Compiler