Historia pytania:
C++ odziedziczył koncepcję dziedziczenia klas z języka C++ oraz metodologii obiektowo-zorientowanych. Pojawienie się dziedziczenia wielokrotnego i wirtualnego skomplikowało strukturę hierarchii, co zwiększyło elastyczność, ale dodało nowe klasy błędów.
Problem:
Dziedziczenie bezpośrednie to sytuacja, w której klasa bezpośrednio dziedziczy od innej bez dodatkowych komplikacji. Dziedziczenie pośrednie występuje, gdy członkowie dziedziczeni są przekazywani poprzez jeden lub więcej pośrednich łańcuchów dziedziczenia. Główna trudność to „problem diamentowy” (diamond problem), w którym wiele ścieżek do jednej klasy bazowej może prowadzić do powielania jej członków w klasach pochodnych.
Rozwiązanie:
Aby zarządzać złożonością, stosuje się dziedziczenie wirtualne, które zapewnia jeden wspólny egzemplarz członka klasy bazowej na całą hierarchię, a nie dla każdego łańcucha.
Przykład kodu:
class A { public: int value; }; class B : public virtual A {}; class C : public virtual A {}; class D : public B, public C {};
W klasie D będzie tylko jeden egzemplarz członka A::value.
Kluczowe cechy:
Czy dziedziczenie wirtualne może spowodować nieokreślone zachowanie, jeśli nie jest używane przy istnieniu problemu diamentowego?
Jeśli w hierarchii diamentowej nie użyje się dziedziczenia wirtualnego, członkowie klasy bazowej będą się powielać. Może to wprowadzić zamieszanie w logice kodu i prowadzić do niejednoznaczności przy odwoływaniu się do członków klasy bazowej.
W jakich przypadkach nie należy używać dziedziczenia wirtualnego?
Jeśli jesteś pewien, że twoje hierarchie nie formują struktury diamentowej, lub klasa bazowa nie zawiera danych, dziedziczenie wirtualne nie jest wymagane.
Jak zarządzać wywołaniami konstruktorów przy dziedziczeniu wirtualnym?
Konstruktor wirtualnej klasy bazowej jest wywoływany tylko przez najbardziej "niską" klasę pochodną. W klasach pośrednich zabronione jest podawanie argumentów dla wirtualnej klasy bazowej (konstruktor jest wywoływany domyślnie, chyba że zainicjalizować go jawnie w klasie końcowej).
Przykład kodu:
class A { public: A(int x) { /* ... */ } }; class B : public virtual A { public: B() : A(0) {} // błąd: nie można zainicjalizować A tutaj }; class D : public B { public: D() : A(10), B() {} // poprawnie };
W dużym projekcie programista nie zauważył wystąpienia struktury diamentowej — obie klasy pośrednie dziedziczyły bezpośrednio od jednej klasy bazowej. Dostęp do członka klasy bazowej prowadził do niejednoznaczności, kod był trudny do przeczytania.
Zalety:
Wady:
Architekt zauważył problem z wyprzedzeniem i zastosował dziedziczenie wirtualne dla klas pośrednich, a konstruktor wirtualnej klasy bazowej był poprawnie wywoływany tylko w najbardziej dolnej klasie pochodnej.
Zalety:
Wady: