Dziedziczenie wielokrotne
Powracając ponownie do pierwszego wykładu, przypomnijmy sobie przykład z pojazdem. Jeśli przyjrzymy się rysunkowi przedstawiającemu hierarchię obiektów to zobaczymy, że pojazdMarsjański pochodzi zarówno od pojazdCzterokolowy jak i od pojazdKosmiczny. To chyba naturalne, że niektóre obiekty mogą mieć więcej niż jednego przodka :) Przyjrzyjmy się jak coś takiego zapisać
class pojazdCzterokołowy
{
...
};
class pojazdKosmiczny
{
...
};
class pojzadMarsjanski: public: pojazdCzterokolowy, public pojazdKosmiczny
{
...
};
|
|
Listing 6.1
|
- Jak widać, dziedziczenie wielokrotne dokonuje się przez umieszczenie na liście pochodzenia tych klas, które mają być podstawowymi dla danej.
- Klasa może wywodzić się od dowolnej ilości klas podstawowych.
- Dana klasa podstawowa może na liście pochodzenia pojawić się tylko raz.
- Definicja klasy umieszczonej na liście pochodzenia musi być już znana. Nie wystarczy w tym przypadku jedynie wcześniejsza deklaracja.
- Przed nazwami klas podstawowych umieszczamy określenie sposobu dziedziczenia
Konstruktory
Kolejny już raz powracamy do tematu kolejności wywołania konstruktorów. Kolejność ta będzie zgodna z kolejności klas podstawowych na liście pochodzenia. Spójrzmy na przykład poniżej
#include
class podstawa1
{
public:
podstawa1();
};
podstawa1::podstawa1()
{
printf("Konstruktor klasy podstawa1\n");
}
class podstawa2
{
public:
podstawa2();
};
podstawa2::podstawa2()
{
printf("Konstruktor klasy podstawa2\n");
}
class pochodna1: public podstawa1, public podstawa2
{
public:
pochodna1();
};
pochodna1::pochodna1()
{
printf("Konstruktor klasy pochodna1\n");
}
class pochodna2: public podstawa2, public podstawa1
{
public:
pochodna2();
};
pochodna2::pochodna2()
{
printf("Konstruktor klasy pochodna2\n");
}
int main(int argc, char *argv[])
{
pochodna1 p1;
pochodna2 p2;
return 0;
}
|
|
Listing 6.2
|
Konstruktor klasy podstawa1
Konstruktor klasy podstawa2
Konstruktor klasy pochodna1
Konstruktor klasy podstawa2
Konstruktor klasy podstawa1
Konstruktor klasy pochodna2
|
|
Output 6.1
Efekt działania programu z listingu 6.2
|
Wieloznaczność przy wielokrotnym dziedziczeniu
Z wieloznacznością przy wielokrotnym dziedziczeniu będziemy mieć do czynienia gdy pewne wyrażenie będzie mogło odnosić się do składnika więcej niż jednej klasy. Teraz może powiem to po ludzku :)) Załóżmy, że mamy dwie klasy podstawowe i w każdej z nich jest zmienna int x. Jeśli teraz w klasie pochodnej odwołamy się do zmiennej x klasy podstawowej, to którą będziemy mieli na myśli? Zobaczmy ponizej jak to rozwiązać.
#include
class p1
{
public:
int x;
};
class p2: public p1
{
public:
int y;
};
class p3
{
public:
int x;
int y;
};
class p4: public p2, public p3
{
public:
//blad p4(){x=5;y=7;}
p4()
{
p1::x=5;
p3::y=7;
}
};
int main(int argc, char *argv[])
{
p4 p;
printf("%d %d %d %d\n",p.p1::x,p.p3::x,p.p2::y,p.p3::y);
printf("%d %d %d %d\n",p.p2::x,p.p3::x,p.p2::y,p.p3::y);
return 0;
}
|
|
Listing 6.3
|
5 7667224 2013313654 7
5 7667224 2013313654 7
|
|
Output 6.2
Efekt działania programu z listingu 6.3
|
In constructor `p4::p4()':
request for member `x' is ambiguous in multiple inheritance
candidates are: int p1::x
int p3::x
request for member `y' is ambiguous in multiple inheritance
candidates are: int p3::y
int p2::y
|
|
Output 6.3
Błędy przy próbie kompilacji programu z listingu 6.3
|
Dziedziczenie wirtualne
Nie trudno wyobrazić sobie następujący przykład dziedziczenia
A
/ \
/ \
/ |
B |
| |
C D
| |
\ /
\ /
E
Z jakiś powodów chcemy aby klasy B i D powstały w oparciu o klase A. Potem lekko modyfikujemy klase B otrzymując C a na końcu postanawiamy C i D połączyć w jedną znaną jako E. Co wtedy? Dostęp do składników będzie niejednoznaczny, gdyż zarówno w klasie C jak i D będą składniki odziedziczone od klasy A; będą to identyczne składniki. Z pomocą przychodzi w takiej sytuacji
dziedziczenie wirtualne. Spójrzmy na poniższy przykład
#include <stdio.h>
class A
{
public:
int x;
};
class B: public virtual A
{
public:
int y;
};
class C: public B
{
public:
int z;
};
class D: public virtual A
{
public:
int n;
};
class E: public C, public D
{
public:
int m;
};
int main()
{
E e1;
e1.x=1;
e1.y=2;
e1.z=3;
e1.n=4;
e1.m=5;
return 0;
}
|
|
Listing 6.4
|
Przy takim sposobie dziedziczenia może się zdarzyć, że klasa podstawowa będzie dziedziczona wirtualnie raz publicznie a raz prywatnie. Co wtedy? W takiej sytuacji efekt jest taki, jakby wszystkie dziedziczenia tej klasy byly publiczne. Jeśli w przykładzie z listingu 6.4 definicję klasy B zastąpimy poniższą
class B: private virtual A
{
public:
int y;
};
|
|
Listing 6.5
|
to wszystko nadal będzie poprawnie działało (odniesienie do zmiennej
x będzie możliwe z wnętrza klasy E mimo że zmienna ta jest prywatne w klasie B).
Kolejność wywołania konstruktorów w przypadku dziedziczenia wirtualnego
O uruchomienie konstruktora klasy dziedziczonej wirtualnie musimy zadbać w klasie najbardziej pochodnej, czyli w klasie, która nie jest już podobiektem żadnej innej klasy. Tutaj ponownie robi się małe zamieszanie. Popatrzmy na poniższy program
#include
class A
{
public:
int x;
A(){printf("Konstruktor 0\n");};
A(int){printf("Konstruktor 1\n");};
A(int, int){printf("Konstruktor 2\n");};
A(int, int ,int){printf("Konstruktor 3\n");};
};
class B: public virtual A
{
public:
int y;
B():A(1){printf("Konstruktor B\n");};
};
class C: public virtual A
{
public:
int z;
C():A(1,2){printf("Konstruktor C\n");};
};
class D: public B, public C
{
public:
int n;
D():A(){printf("Konstruktor D\n");};
};
class E: public D
{
public:
int m;
E():D(){printf("Konstruktor E\n");};
};
int main()
{
printf("-----------\n");
A a(1);
printf("-----------\n");
B b;
printf("-----------\n");
C c;
printf("-----------\n");
D d;
printf("-----------\n");
E e;
printf("-----------\n");
return 0;
}
|
#include
class A
{
public:
int x;
A(){printf("Konstruktor 0\n");};
A(int){printf("Konstruktor 1\n");};
A(int, int){printf("Konstruktor 2\n");};
A(int, int ,int){printf("Konstruktor 3\n");};
};
class B: public A
{
public:
int y;
B():A(1){printf("Konstruktor B\n");};
};
class C
{
public:
int z;
C(){printf("Konstruktor C\n");};
};
class D: public B, public C
{
public:
int n;
D(){printf("Konstruktor D\n");};
};
class E: public D
{
public:
int m;
E():D(){printf("Konstruktor E\n");};
};
int main()
{
printf("-----------\n");
A a(1);
printf("-----------\n");
B b;
printf("-----------\n");
C c;
printf("-----------\n");
D d;
printf("-----------\n");
E e;
printf("-----------\n");
return 0;
}
|
|
Listing 6.6
|
i efekt jego wykonania
-----------
Konstruktor 1
-----------
Konstruktor 1
Konstruktor B
-----------
Konstruktor 2
Konstruktor C
-----------
Konstruktor 0
Konstruktor B
Konstruktor C
Konstruktor D
-----------
Konstruktor 0
Konstruktor B
Konstruktor C
Konstruktor D
Konstruktor E
-----------
|
-----------
Konstruktor 1
-----------
Konstruktor 1
Konstruktor B
-----------
Konstruktor C
-----------
Konstruktor 1
Konstruktor B
Konstruktor C
Konstruktor D
-----------
Konstruktor 1
Konstruktor B
Konstruktor C
Konstruktor D
Konstruktor E
-----------
|
|
Output 6.4
Efekt działania programu z listingu 6.6
|
Wskaźniki na...
Wskaźnik do obiektu klasy pochodnej może być niejawnie przekształcony na wskaźnik dostępnej jednoznacznie klasy podstawowej.
Referencja obiektu klasy pochodnej może być niejawnie przekształcona na referencję jednoznacznie dostępnej klasy podstawowej.
#include
class A
{
public:
int x;
};
class B: public A
{
public:
int y;
};
int main()
{
A * wsk1, a1;
B * wsk2, b1;
wsk1=&a1;
wsk2=&b1;
wsk1=wsk2;
wsk1=&a1;
wsk2=&b1;
//wsk2=wsk1;
return 0;
}
|
|
Listing 6.7
|
Przy próbie kompilacji ze zdjętym komentarzem otrzymamy następujący komunikat
[Warning] In function `int main()':
invalid conversion from `A*' to `B*'
|
|
Output 6.5
Efekt działania programu z listingu 6.7 ze zdjętym komentarzem
|