Wykład 6
Dziedziczenie (2)
Dziedziczenie wielokrotne

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

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