W C++ hierarchie klas mogą powodować problem diamentowy (diamond problem), gdy z jednej klasy bazowej dziedziczy dwóch potomków, a następnie tworzona jest kolejna klasa, która dziedziczy od tych dwóch. W takim przypadku obiekt klasy dziedziczącej będzie zawierał dwie niezależne kopie klasy bazowej. Aby rozwiązać ten problem, w C++ wprowadzono dziedziczenie wirtualne.
Jeśli użyjemy zwykłego dziedziczenia wielokrotnego:
class A { public: int x; }; class B : public A {}; class C : public A {}; class D : public B, public C {};
Obiekt D będzie zawierał 2 kopie A: jedną przez B, drugą — przez C. To powoduje niejednoznaczności w dostępie do A::x i nieuzasadnione wydatki pamięci.
Dziedziczenie wirtualne eliminuje duplikację klasy bazowej. Jeden egzemplarz klasy bazowej A będzie używany przez wszystkich potomków:
class A { public: int x; }; class B : public virtual A {}; class C : public virtual A {}; class D : public B, public C {};
Teraz D zawiera tylko jedną kopię A, a niejednoznaczność z dostępem do A::x zostaje rozwiązana.
Kluczowe cechy:
Dlaczego nie można rozwiązać problemu diamentowego za pomocą jawnego wskazania ścieżki przez operator rozdzielenia zakresu?
Operator rozdzielenia zakresu rozwiązuje tylko niejednoznaczność dostępu do członków klasy bazowej, ale nie eliminuje zbędnych kopii samej klasy bazowej. Problem podwójnej inicjalizacji i podwójnego przechowywania danych pozostaje.
W jakiej kolejności są wywoływane konstruktory przy dziedziczeniu wirtualnym?
Konstruktory wirtualnych klas bazowych są wywoływane w pierwszej kolejności i tylko raz, przez konstruktor najdalej położonego w hierarchii dziedziczenia klasy.
Przykład:
class A { public: A() { std::cout << "A "; } }; class B : public virtual A { public: B() { std::cout << "B "; } }; class C : public virtual A { public: C() { std::cout << "C "; } }; class D : public B, public C { public: D() { std::cout << "D "; } }; D d; // Wydanie: A B C D
Czy konieczne jest deklarowanie dziedziczenia wirtualnego zarówno w deklaracji, jak i w definicji klasy?
Tak, dziedziczenie wirtualne musi być wskazane wszędzie, gdzie odbywa się dziedziczenie od odpowiedniej klasy bazowej. W przeciwnym razie błąd kompilacji jest nieunikniony, a dziedziczenie wielokrotne stanie się zwykłym.
Złożona hierarchia, w której nie zastosowano dziedziczenia wirtualnego, przez co w obiekcie znajduje się dwie kopie ustawień:
Zalety:
Wady:
Zastosowano dziedziczenie wirtualne z synchronizowanym konstruktorem najwyższego potomka:
Zalety:
Wady: