background image

Polimorfizm

†

Dziedziczenie wielokrotne

†

Funkcje wirtualne 

†

Klasy abstrakcyjne 

†

Mechanizm polimorfizmu

background image

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

background image

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

background image

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

background image

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

background image

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 w klasie pochodnej ?  (Składnik ten 

powtarza się w wielu klasach podstawowych)

background image

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)

background image

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();

background image

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;

background image

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 )

background image

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

background image

Klasy wirtualne

†

Dziedziczenie wirtualne stosujemy wtedy, 

gdy chcemy mieć lokalny punkt dzielenia 

się informacją

TZerowa

TPochodna: 

TPierwsza, TDruga, TZerowa

TPierwsza

TDruga

TZerowa

background image

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
{. . .};

background image

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

background image

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

background image

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++

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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

background image

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


Document Outline