Dziedziczenie wielokrotne w C++ jest potężnym, ale trudnym narzędziem, które często prowadzi do problemów, jeśli nie jest używane ostrożnie — problemów takich jak Problem Diamentowy.
W tym artykule omówimy problem diamentów, w jaki sposób wynika on z wielokrotnego dziedziczenia i co możesz zrobić, aby go rozwiązać.
Wielokrotne dziedziczenie w C++
Dziedziczenie wielokrotne to funkcja programowania obiektowego (OOP) gdzie podklasa może dziedziczyć z więcej niż jednej nadklasy. Innymi słowy, klasa podrzędna może mieć więcej niż jednego rodzica.
Poniższy rysunek przedstawia obrazową reprezentację wielu dziedziczeń.
Na powyższym schemacie klasa C ma klasa A oraz klasa B jako jego rodzice.
Jeśli weźmiemy pod uwagę scenariusz z prawdziwego życia, dziecko dziedziczy po ojcu i matce. Tak więc Dziecko może być reprezentowane jako klasa pochodna z „Ojcem” i „Matką” jako rodzicami. Podobnie możemy mieć wiele takich rzeczywistych przykładów wielokrotnego dziedziczenia.
W dziedziczeniu wielokrotnym konstruktory dziedziczonej klasy są wykonywane w kolejności, w jakiej są dziedziczone. Z drugiej strony destruktory są wykonywane w odwrotnej kolejności ich dziedziczenia.
Teraz zilustrujmy wielokrotne dziedziczenie i zweryfikujmy kolejność konstruowania i niszczenia obiektów.
Ilustracja kodu wielokrotnego dziedziczenia
Dla ilustracji dziedziczenia wielokrotnego, dokładnie zaprogramowaliśmy powyższą reprezentację w C++. Kod programu znajduje się poniżej.
#włączać
używając standardowej przestrzeni nazw;
klasa A //klasa bazowa A z konstruktorem i destruktorem
{
publiczny:
A() { cout << "klasa A:: Konstruktor" << endl; }
~A() { cout << "klasa A:: Destruktor" << endl; }
};
klasa B //klasa bazowa B z konstruktorem i destruktorem
{
publiczny:
B() { cout << "klasa B:: Konstruktor" << endl; }
~B() { cout << "klasa B:: Destruktor" << endl; }
};
klasa C: public B, public A //pochodna klasa C dziedziczy klasę A, a następnie klasę B (zwróć uwagę na kolejność)
{
publiczny:
C() { cout << "klasa C:: Konstruktor" << endl; }
~C() { cout << "klasa C:: Destruktor" << endl; }
};
int main() {
Cc;
zwróć 0;
}
Wynik jaki uzyskujemy z powyższego programu jest następujący:
klasa B:: Konstruktor
klasa A:: Konstruktor
klasa C:: Konstruktor
klasa C:: Destruktor
klasa A:: Destruktor
klasa B:: Destruktor
Teraz, jeśli sprawdzimy dane wyjściowe, zobaczymy, że konstruktory są wywoływane w kolejności B, A i C, podczas gdy destruktory są w odwrotnej kolejności. Teraz, gdy znamy podstawy wielokrotnego dziedziczenia, przechodzimy do omówienia Problemu Diamentów.
Wyjaśnienie problemu diamentów
Diamentowy problem pojawia się, gdy klasa potomna dziedziczy po dwóch klasach rodzicielskich, które mają wspólną klasę dziadków. Ilustruje to poniższy schemat:
Tutaj mamy klasę Dziecko dziedziczenie po zajęciach Ojciec oraz Mama. Te dwie klasy z kolei dziedziczą klasę Osoba ponieważ zarówno Ojciec, jak i Matka są Osobą.
Jak pokazano na rysunku, klasa Dziecko dziedziczy cechy klasy Osoba dwukrotnie – raz od Ojca i ponownie od Matki. Powoduje to niejednoznaczność, ponieważ kompilator nie rozumie, w którą stronę iść.
Ten scenariusz daje początek wykresowi dziedziczenia w kształcie rombu i jest znany pod nazwą „Problem diamentu”.
Kodowa ilustracja problemu diamentowego
Poniżej przedstawiliśmy programowo powyższy przykład dziedziczenia w kształcie rombu. Kod podany jest poniżej:
#włączać
używając standardowej przestrzeni nazw;
class Osoba { //klasa Osoba
publiczny:
Osoba (int x) { cout << "Osoba:: Osoba (int) wywoływana" << endl; }
};
class Ojciec: public Osoba { //class Ojciec dziedziczy Osobę
publiczny:
Ojciec (int x): Osoba (x) {
cout << "Ojciec:: Ojciec (int) wezwany" << endl;
}
};
class Mother: public Person { //class Mother dziedziczy Person
publiczny:
Matka (int x): Osoba (x) {
cout << "Matka:: Matka (int) wezwana" << endl;
}
};
klasa Dziecko: publiczne Ojciec, publiczne Matka { //Dziecko dziedziczy Ojca i Matkę
publiczny:
Dziecko (int x): Matka (x), Ojciec (x) {
cout << "Dziecko:: Dziecko (int) nazwane" << endl;
}
};
int main() {
Dziecko dziecko (30);
}
Poniżej znajduje się wynik tego programu:
Osoba:: Osoba (int) nazwana
Ojciec:: Ojciec (int) zwany
Osoba:: Osoba (int) nazwana
Matka:: Matka (int) nazywana
Dziecko:: Dziecko (int) zwane
Teraz możesz zobaczyć tutaj niejednoznaczność. Konstruktor klasy Person jest wywoływany dwukrotnie: raz przy tworzeniu obiektu klasy Father, a następnie przy tworzeniu obiektu klasy Mother. Właściwości klasy Person są dziedziczone dwukrotnie, co powoduje niejednoznaczność.
Ponieważ Konstruktor klasy Person jest wywoływany dwukrotnie, destruktor zostanie również wywołany dwukrotnie, gdy obiekt klasy Child zostanie zniszczony.
Teraz, jeśli dobrze zrozumiałeś problem, omówmy rozwiązanie Diamentowego Problemu.
Jak rozwiązać problem z diamentami w C++
Rozwiązaniem problemu diamentów jest użycie wirtualny słowo kluczowe. Tworzymy dwie klasy rodzicielskie (które dziedziczą z tej samej klasy dziadków) w klasy wirtualne, aby uniknąć dwóch kopii klasy dziadków w klasie podrzędnej.
Zmieńmy powyższą ilustrację i sprawdźmy wynik:
Ilustracja kodu, aby rozwiązać problem z diamentem
#włączać
używając standardowej przestrzeni nazw;
class Osoba { //klasa Osoba
publiczny:
Osoba() { cout << "Osoba:: Osoba() wywołana" << endl; } //Konstruktor bazowy
Osoba (int x) { cout << "Osoba:: Osoba (int) wywoływana" << endl; }
};
class Father: virtual public Person { //class Father dziedziczy Person
publiczny:
Ojciec (int x): Osoba (x) {
cout << "Ojciec:: Ojciec (int) wezwany" << endl;
}
};
class Mother: virtual public Person { //class Mother dziedziczy Person
publiczny:
Matka (int x): Osoba (x) {
cout << "Matka:: Matka (int) wezwana" << endl;
}
};
class Child: public Father, public Mother { //class Child dziedziczy ojca i matkę
publiczny:
Dziecko (int x): Matka (x), Ojciec (x) {
cout << "Dziecko:: Dziecko (int) nazwane" << endl;
}
};
int main() {
Dziecko dziecko (30);
}
Tutaj użyliśmy wirtualny słowo kluczowe, gdy klasy Ojciec i Matka dziedziczą klasę Osoba. Jest to zwykle nazywane „dziedziczeniem wirtualnym”, które gwarantuje, że przekazywane jest tylko jedno wystąpienie dziedziczonej klasy (w tym przypadku klasy Person).
Innymi słowy, klasa Child będzie miała pojedynczą instancję klasy Person, wspólną dla klas Father i Mother. Mając jedno wystąpienie klasy Person, rozwiązuje się niejednoznaczność.
Dane wyjściowe powyższego kodu podano poniżej:
Osoba:: Osoba() nazywana
Ojciec:: Ojciec (int) zwany
Matka:: Matka (int) nazywana
Dziecko:: Dziecko (int) zwane
Tutaj widać, że konstruktor klasy Person jest wywoływany tylko raz.
Jedną rzeczą, na którą należy zwrócić uwagę w przypadku wirtualnego dziedziczenia, jest to, że nawet jeśli sparametryzowany konstruktor Klasa Person jest jawnie wywoływana przez konstruktorów klas Father i Mother poprzez inicjalizację listy, zostanie wywołany tylko konstruktor bazowy klasy Person.
Dzieje się tak, ponieważ istnieje tylko jedna instancja wirtualnej klasy bazowej, która jest współużytkowana przez wiele klas, które z niej dziedziczą.
Aby zapobiec wielokrotnemu uruchamianiu konstruktora bazowego, konstruktor wirtualnej klasy bazowej nie jest wywoływany przez dziedziczącą po nim klasę. Zamiast tego konstruktor jest wywoływany przez konstruktora konkretnej klasy.
W powyższym przykładzie klasa Child bezpośrednio wywołuje konstruktor bazowy dla klasy Person.
Związane z: Przewodnik dla początkujących po bibliotece szablonów standardowych w C++
Co zrobić, jeśli musisz wykonać sparametryzowany konstruktor klasy bazowej? Możesz to zrobić, wyraźnie nazywając go w klasie Child, a nie w klasach Father lub Mother.
Diamentowy problem w C++, rozwiązany
Diamentowy problem to niejednoznaczność, która pojawia się w przypadku wielokrotnego dziedziczenia, gdy dwie klasy rodzicielskie dziedziczą z tej samej klasy dziadków, a obie klasy rodzicielskie są dziedziczone przez jedną klasę potomną. Bez użycia wirtualnego dziedziczenia klasa potomna dziedziczyłaby dwukrotnie właściwości klasy dziadków, co prowadziłoby do niejednoznaczności.
Może się to często pojawiać w prawdziwym kodzie, dlatego ważne jest, aby rozwiązać tę niejednoznaczność, gdy zostanie zauważona.
Diamentowy problem jest rozwiązywany za pomocą wirtualnego dziedziczenia, w którym wirtualny słowo kluczowe jest używane, gdy klasy nadrzędne dziedziczą ze współdzielonej klasy dziadków. W ten sposób tworzona jest tylko jedna kopia klasy dziadków, a konstrukcja obiektu klasy dziadków jest wykonywana przez klasę potomną.
Chcesz nauczyć się programowania, ale nie wiesz od czego zacząć? Te projekty programistyczne i samouczki dla początkujących pozwolą Ci zacząć.
Czytaj dalej
- Programowanie
- Programowanie C
Zapisz się do naszego newslettera
Dołącz do naszego newslettera, aby otrzymywać porady techniczne, recenzje, bezpłatne e-booki i ekskluzywne oferty!
Kliknij tutaj, aby zasubskrybować