Polimorfizm
Dziedziczenie wielokrotne
Funkcje wirtualne
Klasy abstrakcyjne
Mechanizm polimorfizmu
Dziedziczenie wielokrotne
Dziedziczenie wielokrotne zachodzi wtedy,
jeżeli klasa ma więcej niż jedną klasę
podstawową, dziedziczoną bezpośrednio
Używane jest znacznie rzadziej niż
jednokrotne
Zastosowanie:
Jeżeli zachodzi potrzeba powiązania ze sobą
niezależnych klas
Jeżeli klasa pochodna ma cechy dwóch lub
więcej już zdefiniowanych klas
Przykład dziedziczenia
wielokrotnego
Kod programu:
class TSamochod {
//PIERWSZA KLASA PODSTAWOWA
int Kolo[4];
...};
class TLodka {
//DRUGA KLASA PODSTAWOWA
float Ster;
...};
class TAmfibia: public TSamochod, public TLodka {
//...
};
TSamochod
TLodka
TAmfibia
Uwagi dotyczące dziedziczenia
wielokrotnego
Każda klasa podstawowa jest wymieniana
jednokrotnie. Nie jest dopuszczalna np.
deklaracja
class TAmfibia: TSamochod,TLodka,TSamochod {};
Każda z klas podstawowych ma własny
sposób dziedziczenia
Modyfikatory: public, protected, private
Domniemany jest private, jak przy dziedziczeniu
jednokrotnym
Nie wpisanie modyfikatora przed nazwą każdej
klasy podstawowej oznacza dziedziczenie private
Definicje wszystkich klas podstawowych
muszą być umieszczone przed deklaracją
klasy pochodnej
Konstruktor klasy pochodnej przy
dziedziczeniu wielokrotnym
Konstruktor klasy pochodnej powinien
zawierać wywołania konstruktorów klas
podstawowych bezpośrednio w liście
inicjalizacyjnej
Wywołanie można pominąć, jeżeli klasa
podstawowa ma konstruktor domniemany
Konstruktory klas podstawowych są
wywoływane w kolejności umieszczenia na
liście pochodzenia w deklaracji klasy
Konstruktory obiektów składowych są
wywoływane w kolejności umieszczenia na
liście inicjalizacyjnej klasy
Wieloznaczność przy dziedziczeniu
wielokrotnym
class TPierwsza {
int a;
float x;};
class TDruga {
int a;
float b;};
class TPochodna: public TPierwsza, public TDruga
{
//void Wypisz(void){ cout << a; }; //???
};
W jaki sposób można uzyskać dostęp do
składnika a w klasie pochodnej ? (Składnik ten
powtarza się w wielu klasach podstawowych)
Sposoby dostępu do składnika
wieloznacznego
1) Posłużenie się operatorem zakresu w klasie
TPochodna:
cout << TPierwsza::a;
cout << TDruga::a;
Wady takiego rozwiązania
W ewentualnych klasach pochodnych od
TPochodna nadal występuje wieloznaczność
składnika a;
Dotyczy to WSZYSTKICH kolejnych klas
pochodnych
Jeżeli wieloznaczna jest nazwa funkcji
wirtualnej, przy powyższym wywołaniu zostaje
zablokowany mechanizm wywoływania tych
funkcji (polimorfizm)
Sposoby dostępu do składnika
wieloznacznego
2) Zdefiniowanie w klasie pochodnej funkcji o
nazwie jak składnik w klasie podstawowej:
TPochodna ... {
int a(){ return TPierwsza::a };
Zalety:
przestają być aktualne wady rozwiązania
pierwszego
Wady:
a jest teraz funkcją, więc przy odniesieniu się
należy stosować nawiasy:
cout << a();
Bliższe pokrewieństwo
a wieloznaczność
Cecha nie działa w kompilatorze
Borland C++ 3.1
TZerowa
int b;
TPochodna
void W(){ cout << b; };
TPierwsza
int xxx;
TDruga
int b;
Standardowe konwersje przy
dziedziczeniu
Jeżeli
Klasa jest dziedziczona publicznie
Konwersja jest jednoznaczna
Wskaźnik obiektu klasy pochodnej może być
przekształcony na wskaźnik jednoznacznie dostępnej
klasy podstawowej
( wskaźnikiem do obiektu klasy podstawowej można
pokazywać obiekt klasy pochodnej )
Referencja obiektu klasy pochodnej może być
przekształcona na referencję jednoznacznie dostępnej
klasy podstawowej
( referencja obiektu klasy podstawowej może być
wykorzystana do obiektu klasy pochodnej )
Sytuacje gdy następuje konwersja
standardowa
Przesłanie obiektu do funkcji przez wskaźnik lub
referencję (poprzedni przykład) UWAGA! nie można
przesłać przez wartość !
Rezultat zwracany przez funkcję – jeżeli funkcja ma
zwracać wskaźnik do obiektu klasy podstawowej, po
słowie return można wpisać wskaźnik do obiektu klasy
pochodnej
Przeładowany operator pracujący na referencji do
obiektu klasy podstawowej może być zastosowany dla
obiektu klasy pochodnej
Podczas inicjalizacji obiektu klasy podstawowej
(konstruktor kopiujący) jako wzorzec można podać
obiekt klasy pochodnej
Klasy wirtualne
Dziedziczenie wirtualne stosujemy wtedy,
gdy chcemy mieć lokalny punkt dzielenia
się informacją
TZerowa
TPochodna:
TPierwsza, TDruga, TZerowa
TPierwsza
TDruga
TZerowa
Sposób deklaracji wirtualnej klasy
podstawowej
W liście pochodzenia należy wpisać słowo
virtual przed nazwą klasy
Klasa może być równocześnie dziedziczona
wirtualnie i niewirtualnie
Deklaracje:
class TPierwsza: public virtual TZerowa
{. . .};
class TPochodna: public TPierwsza,
public TDruga, public virtual TZerowa
{. . .};
Funkcje wirtualne
Definicja słowa wirtualny – teoretycznie
możliwy, mogący zaistnieć
Funkcja wirtualna zdefiniowana w klasie
podstawowej może być ponownie zdefiniowana
w klasie potomnej
To ponowne zdefiniowanie w odróżnieniu od
zasłaniania nazw jest „inteligentne”
Mechanizm funkcji wirtualnych ma
zastosowanie, gdy obiekt jest
pokazywany wskaźnikiem, lub dostępny
przez referencję, a jego typ nie zgadza się
z typem wskaźnika
Polimorfizm
Polimorfizm (gr. wielość postaci, form) jest
to mechanizm wywoływania funkcji
zadeklarowanych jako wirtualne
Jeżeli funkcja nie jest wirtualna, wtedy
wskaźnik do klasy podstawowej lub
referencja do obiektu tej klasy wywoła
funkcję Z KLASY PODSTAWOWEJ
W przypadku funkcji wirtualnej może zostać
wywołana funkcja z klasy pochodnej
nawet, jeżeli np. moduł z definicją klasy
podstawowej jest w postaci skompilowanej
Zalety stosowania polimorfizmu
Dodanie do programu nowej klasy,
dziedziczonej z klasy już istniejącej nie
wymaga dokonywania poprawek we
wszystkich miejscach, gdzie mamy do
czynienia ze wskaźnikiem lub referencją do
obiektów klasy podstawowej
W ten sposób program może być łatwo
rozbudowywany
Świadczy to o rozszerzalności kodu
programu w języku C++
Koszty stosowania polimorfizmu
Pytanie: dlaczego wszystkie funkcje
składowe obiektów nie są wirtualne?
Obiekt klasy zawierającej funkcje
wirtualne zajmuje więcej pamięci
Operacje na takim obiekcie przebiegają
wolniej
Mechanizmy polimorfizmu mają
praktyczne zastosowanie w połączeniu z
dziedziczeniem
Właściwości funkcji wirtualnych
Funkcja staje się wirtualną jeżeli
W deklaracji zostanie użyte słowo virtual
W klasie podstawowej zostało użyte słowo
virtual
dla identycznej* funkcji
Słowo virtual nie musi (ale może)
powtarzać się w klasach pochodnych
Słowo virtual piszemy tylko przy
deklaracji klasy, nie pojawia się w definicji
*Funkcja identyczna = taka sama nazwa, parametry
i typ wartości zwracanej
Dodatkowe informacje
Jeżeli funkcja wirtualna ma inny zakres
widoczności w klasie podstawowej a inny w
pochodnej, wtedy o dostępie decyduje
klasa wskaźnika (referencji)
Funkcje wirtualne nie mogą być statyczne
(static)
Funkcje wirtualne mogą być używane przez
funkcje lub klasy zaprzyjaźnione
Wczesne i późne wiązanie
Wiązanie – połączenie wywołań funkcji
z adresami tych funkcji w pamięci
(instrukcja skoku do podprogramu)
Jeżeli nie są wykorzystywane funkcje
wirtualne następuje tzw. wczesne wiązanie
(ang. early binding) – podczas kompilacji
Jeżeli są wykorzystywane funkcje wirtualne
następuje tzw. późne wiązanie (ang. late
binding) – podczas wykonywania programu
Funkcja wirtualna a inline
Kod funkcji inline jest bezpośrednio
wstawiany do kodu programu na etapie
kompilacji
Funkcje wirtualne MOGĄ BYĆ inline
Jeżeli wywołanie funkcji jest polimorficzne
(późne wiązanie) inline jest ignorowane
W przypadku wczesnego wiązania
(bezpośrednie wywołanie funkcji z obiektu)
funkcja jest traktowana jako inline
Klasy abstrakcyjne
Klasa abstrakcyjna to klasa podstawowa,
która nie reprezentuje żadnego
konkretnego obiektu
Przykład: ssak, figura, bryła
Klasy abstrakcyjne są przeznaczone
wyłącznie do dziedziczenia
Mechanizm polimorfizmu bardzo często
jest łączony z klasami abstrakcyjnymi
W klasie abstrakcyjnej używamy funkcji
wirtualnych jeżeli
Zachowanie opisane funkcją jest cechą kilku klas
potomnych (np. narysowanie figury)
W każdej klasie potomnej to zachowanie jest
inaczej realizowane
Klasy czysto abstrakcyjne
W ogólnym przypadku można utworzyć
obiekt klasy podstawowej posiadającej
funkcje wirtualne
Aby utworzyć klasę czysto abstrakcyjną,
należy przynajmniej jedną z jej funkcji
wirtualnych zdefiniownać jako „czysto
wirtualną” (ang. pure virtual):
void virtual Funkcja()=0;
Klasa czysto abstrakcyjna NIE MOŻE mieć
definiowanych obiektów
Cechy klas abstrakcyjnych
Nie można definiować obiektu takiej klasy
np. TFigura F;
Nie można zdefiniować funkcji, której
argumentem byłby obiekt tej klasy
przekazywany przez wartość
np. void Funkcja(TFigura F);
Nie można zdefiniować funkcji zwracającej
obiekt takiej klasy
Nie można stosować nazwy klasy czysto
abstrakcyjnej do jawnej konwersji
Destruktor wirtualny
Jeżeli w klasie występuje przynajmniej jedna
funkcja wirtualna, destruktor tej klasy
powinien być wirtualny
Destruktor jest wyjątkiem od ogólnej zasady
o identyczności funkcji wirtualnych
Destruktory wszystkich klas pochodnych od
klasy z destruktorem wirtualnym są wirtualne
Zadeklarowanie destruktora wirtualnego
umożliwi poprawne zwalnianie pamięci
podczas niszczenia obiektów pokazywanych
przez wskaźnik klasy podstawowej
Wirtualny konstruktor ?
Nie można utworzyć konstruktora wirtualnego
Zamiast tego można zdefiniować funkcję wirtualną
która:
Wywoła konstruktor domniemany klasy potomnej
lub
Wywoła konstruktor kopiujący klasy potomnej
Nazwy tych funkcji mogą być dowolne
Funkcje zwracają wskaźnik do obiektu klasy
podstawowej
Można zdefiniować obie wymienione funkcje
równocześnie