C++/Dziedziczenie wielokrotne

Z Wikibooks, biblioteki wolnych podręczników.
< C++

Język C++, w odróżnieniu od wielu popularnych języków, np. Javy, dopuszcza dziedziczenie wielobazowe (dziedziczenie wielokrotne), tj. klasa może dziedziczyć po więcej niż jednej klasie. To powoduje, że w ogólnym przypadku nie mamy do czynienia z drzewiastą hierarchią klas, lecz skierowanym grafem acyklicznym dziedziczenia.

Klasa pochodna ma dostęp do wszystkich pól z klas bazowych oraz udostępnia pola i metody zgodnie z podanymi regułami widoczności (private/protected/public). Jeśli pola lub metody w klasach bazowych powtarzają się, wówczas konieczna jest dodatkowa klasyfikacja nazwą klasy, jak w przykładzie poniżej.

#include <iostream>

class BazowaA {
protected:
    int licznik;
public:
    void wyswietl() {
        std::cout << "A::wyswietl()" << '\n';
    }
};

class BazowaB {
protected:
    int licznik;
public:
    void wyswietl() {
        std::cout << "B::wyswietl()" << '\n';
    }
};

class Klasa: public BazowaA, public BazowaB {
public:
    void wyzeruj() {
        BazowaA::licznik = 0;
        BazowaB::licznik = 0;
    }
};

int main() {
    Klasa k;

    k.BazowaA::wyswietl();
    k.BazowaB::wyswietl();
}


Dziedziczenie wirtualne[edytuj]

Dziedzicznie wirtualne jest specjalnym przypadkiem w dziedziczeniu wielobazowym, które stosuje się, gdy z jakiegoś powodu jedna z klas staje się wielokrotnie przodkiem (bezpośrednio lub pośrednio) innej klasy. Np.

class Bazowa {
protected:
    void wyswietl() {};
};

class Posrednia: public Bazowa {};

class Klasa: public Bazowa, public Posrednia {};

Tutaj to klasa Bazowa jest przodkiem Klasy: raz bezpośrednio, raz poprzez klasę Pośrednią. Oczywiście nie jest to błąd, ale niesie ze sobą następujące niedogodności:

  • Pola z klasy Bazowa powtarzają się 2 razy, skutkiem czego sizeof(Klasa) > 2 * sizeof(Bazowa). To na pierwszy rzut oka może wydawać się mało istotne, bo zwykle rozmiar klasy jest niewielki. Jednak co gdy przechowujemy tysiące lub miliony instancji? Wówczas ten narzut może okazać się znaczący.
  • Zwykle też chcemy, aby to właśnie klasa Bazowa dostarczała pewnych metod lub pól dla klas pochodnych, a w tym przypadku odwołanie do metody Wyswietl musi być kwalifikowane nazwą klasy nadrzędnej, co jest mało wygodne.

Zatem jeśli chcemy wyraźnie wskazać, że jakaś klasa jest bazową dla wszystkich potomków, to przy w deklaracji musimy dodać słowo kluczowe virtual przy nazwie klasy z której dziedziczymy. Wówczas zniknie niejednoznaczność odwołań do jej składowych, natomiast jej pola zostaną umieszczone w klasie pochodnej tylko raz. W przykładzie poniżej rozmiar Klasy to 1kB + kilka bajtów, bez dziedziczenia wirtualnego byłoby to ponad 2kB, oraz oczywiście wywołanie metody wyswietl (używając takiego zapisu) byłoby niemożliwe.

class Bazowa {
    char bufor1kB[1024];
public:
    void wyswietl() {}
};

class Posrednia1: public virtual Bazowa {};

class Posrednia2: public virtual Bazowa {};

class Klasa: public Posrednia1, public Posrednia2 {};

int main() {
    Klasa k;

    std::cout << sizeof(Klasa) << '\n';

    k.wyswietl();
}