Dlaczego inna odpowiedź?
Cóż, wiele postów na SO i artykuły na zewnątrz mówi, że problem z diamentami można rozwiązać, tworząc jedną instancję Azamiast dwóch (po jednej dla każdego rodzica D), rozwiązując w ten sposób niejednoznaczność. Jednak nie dało mi to pełnego zrozumienia procesu, skończyło się na jeszcze większej liczbie pytań, takich jak
- co jeśli
Bi Cspróbuje utworzyć różne instancje Anp. wywołania sparametryzowanego konstruktora z różnymi parametrami ( D::D(int x, int y): C(x), B(y) {})? Która instancja Awoli zostanie wybrana jako część D?
- co się stanie, jeśli użyję dziedziczenia niewirtualnego
B, ale wirtualnego C? Czy wystarczy do stworzenia jednej instancji Aw D?
- czy powinienem zawsze domyślnie używać dziedziczenia wirtualnego jako środka zapobiegawczego, ponieważ rozwiązuje ono możliwy problem z diamentami przy niewielkich kosztach wydajności i bez innych wad?
Brak możliwości przewidzenia zachowania bez wypróbowania próbek kodu oznacza niezrozumienie koncepcji. Oto, co pomogło mi w omówieniu dziedziczenia wirtualnego.
Podwójne A
Po pierwsze, zacznijmy od tego kodu bez wirtualnego dziedziczenia:
#include<iostream>
using namespace std;
class A {
public:
A() { cout << "A::A() "; }
A(int x) : m_x(x) { cout << "A::A(" << x << ") "; }
int getX() const { return m_x; }
private:
int m_x = 42;
};
class B : public A {
public:
B(int x):A(x) { cout << "B::B(" << x << ") "; }
};
class C : public A {
public:
C(int x):A(x) { cout << "C::C(" << x << ") "; }
};
class D : public C, public B {
public:
D(int x, int y): C(x), B(y) {
cout << "D::D(" << x << ", " << y << ") "; }
};
int main() {
cout << "Create b(2): " << endl;
B b(2); cout << endl << endl;
cout << "Create c(3): " << endl;
C c(3); cout << endl << endl;
cout << "Create d(2,3): " << endl;
D d(2, 3); cout << endl << endl;
cout << "d.B::getX() = " << d.B::getX() << endl;
cout << "d.C::getX() = " << d.C::getX() << endl;
}
Przejdźmy przez wyjście. Wykonywanie B b(2);tworzy A(2)zgodnie z oczekiwaniami, to samo dla C c(3);:
Create b(2):
A::A(2) B::B(2)
Create c(3):
A::A(3) C::C(3)
D d(2, 3);potrzebuje zarówno Bi Ckażdy z nich tworząc swój własny A, więc mamy podwójne Aw d:
Create d(2,3):
A::A(2) C::C(2) A::A(3) B::B(3) D::D(2, 3)
Z tego powodu d.getX()powoduje błąd kompilacji, ponieważ kompilator nie może wybrać Ainstancji, dla której ma wywołać metodę. Nadal możliwe jest wywołanie metod bezpośrednio dla wybranej klasy nadrzędnej:
d.B::getX() = 3
d.C::getX() = 2
Wirtualność
Teraz dodajmy dziedziczenie wirtualne. Korzystanie z tego samego przykładu kodu z następującymi zmianami:
class B : virtual public A
...
class C : virtual public A
...
cout << "d.getX() = " << d.getX() << endl;
cout << "d.A::getX() = " << d.A::getX() << endl;
...
Przejdźmy do tworzenia d:
Create d(2,3):
A::A() C::C(2) B::B(3) D::D(2, 3)
Jak widać, Ajest tworzony z domyślnym konstruktorem ignorującym parametry przekazane z konstruktorów Bi C. Ponieważ niejednoznaczność zniknęła, wszystkie wywołania getX()zwracające tę samą wartość:
d.getX() = 42
d.A::getX() = 42
d.B::getX() = 42
d.C::getX() = 42
Ale co, jeśli chcemy wywołać sparametryzowany konstruktor dla A? Można to zrobić poprzez jawne wywołanie go z konstruktora D:
D(int x, int y, int z): A(x), C(y), B(z)
Zwykle klasa może jawnie używać konstruktorów tylko bezpośrednich rodziców, ale istnieje wykluczenie dla przypadku dziedziczenia wirtualnego. Odkrycie tej reguły "kliknęło" dla mnie i bardzo pomogło w zrozumieniu wirtualnych interfejsów:
Kod class B: virtual Aoznacza, że każda dziedziczona klasa Bjest teraz odpowiedzialna za tworzenie Asamodzielnie, ponieważ Bnie zrobi tego automatycznie.
Mając to na uwadze, łatwo odpowiedzieć na wszystkie pytania, które miałem:
- Podczas
Dtworzenia ani Bnie Cjest odpowiedzialny za parametry A, jest to całkowicie zależne Dtylko od.
Cprzekaże tworzenie Ado D, ale Butworzy własną instancję Aprzywracającą w ten sposób problem z diamentami
- Definiowanie parametrów klasy bazowej w klasie wnuczka, a nie bezpośredniego dziecka, nie jest dobrą praktyką, więc powinno być tolerowane, gdy istnieje problem z diamentami i jest to nieuniknione.