P-Programowanie
Tekst
zmniejsz/powiększ
Kolory
jasne/ciemne/kontrast/brak

Polimorfizm i metody wirtualne

Polimorfizm jest filarem programowania obiektowego, nie tylko jeżeli chodzi o język C++. Daje on programiście dużą elastyczność podczas pisania programu. Polimorfizm jest ściśle związany z metodami wirtualnymi. Złe operowanie mechanizmem polimorfizmu może znacznie spowolnić działanie aplikacji i doprowadzić do poważnych błędów.

Czym jest polimorfizm?

Polimorfizm (wielopostaciowość) jest to cecha programowania obiektowego, umożliwiająca różne zachowanie tych samych metod wirtualnych (funkcji wirtualnych) w czasie wykonywania programu.

Polimorfizm jest słowem zaczerpniętym do informatyki stosunkowo niedawno, podczas rozwoju języków programowania. W języku C++ możemy korzystać z tego mechanizmu za pomocą metod wirtualnych. Dzięki niemu mamy pełną kontrolę nad wykonywanym programem, nie tylko w momencie kompilacji (wiązanie statyczne) ale także podczas działania programu (wiązanie dynamiczne) – niezależnie od różnych wyborów użytkownika.

Ponieważ C++ jest językiem hybrydowym, nie mamy konieczności korzystania z polimorfizmu. Zostanie on automatycznie włączony podczas zadeklarowania przynajmniej jednej metody wirtualnej w danej klasie.

Metody wirtualne

W internecie często używana jest nazwa funkcji wirtualnej, jednak jest ona dość mylna i nie do końca zgodna z konwencją programowania obiektowego. Funkcja wirtualna musi być funkcją składową danej klasy, a więc generalnie jest metodą. Zwykłej funkcji nie da się zadeklarować jako wirtualna.

Krótkie przypomnienie
  • Podczas dziedziczenia obiekt klasy pochodnej może być wskazywany przez wskaźnik typu klasy bazowej.
  • Typem statycznym obiektu wskazywanego przez wskaźnik jest typ tego wskaźnika. Typem dynamicznym obiektu wskazywanego przez wskaźnik jest typ na jaki dany wskaźnik wskazuje.

Dwa powyższe fakty dobrze zobrazuje ten kod:

Dzięki tym informacjom możemy napisać zwięzłą definicję metody wirtualnej:

Metoda wirtualna jest to funkcja składowa klasy poprzedzona słowem kluczowym virtual, której sposób wywołania zależy od typu dynamicznego wskaźnika, a nie od typu statycznego.

Definicja wydaje się trudna do zrozumienia, ale schemat jest bardzo prosty. Zobaczymy jak wygląda mechanizm przesłaniania funkcji podczas dziedziczenia, bez użycia metod wirtualnych i polimorfizmu. Aby funkcje zostały przesłonięte muszą mieć taką samą nazwę, argumenty oraz typ zwracany:

Sytuacja nas nie zaskakuje. To, która metoda zostanie wywołana zależy od typu wskaźnika na obiekt. Jest to wspomniane wcześniej wiązanie statyczne. Kompilator już podczas kompilacji programu wie, jakiego typu statycznego są obiekty i jakie metody mają zostać wywołane.

Dzięki dodaniu do naszego kodu metod wirtualnych, uruchomimy mechanizm polimorfizmu. Wczesne wiązanie statyczne nie będzie miało wtedy żadnego znaczenia, ponieważ to która funkcja zostanie wywołana będzie zależało od późnego wiązania dynamicznego.

Słowo kluczowe virtual dobrze spełniło swoje zadanie. Widzimy jak wywołania metod są zależne od typu dynamicznego. Słowo virtual wystarczy dodać jedynie w klasie bazowej, nie ma konieczności powtarzania go w klasach pochodnych.

Zastanawiasz się pewnie, do czego potrzebny jest polimorfizm oraz metody wirtualne? Bez używania polimorfizmu, programista musiał już na etapie pisania programu, wiedzieć jak będzie się on zachowywał. To za sprawą wczesnego wiązania, które musi być dostarczone kompilatorowi w momencie kompilacji i linkowania.

W przypadku użycia polimorfizmu dostajemy nieograniczone możliwości projektowania aplikacji, gdzie zachowanie programu może się ciągle zmieniać.

Przykład zastosowania polimorfizmu

Oto prosty przykład zastosowania polimorfizmu: Posiadamy klasę bazową Pojazd oraz trzy klasy pochodne: Samochod, Rower i Rolki. Wszystkie klasy mają zdefiniowaną prostą metodę zatrzymaj() odpowiedzialną za zatrzymanie pojazdu danego typu.

Deklarujemy tablicę wskaźników na obiekty klasy Pojazd (możemy ponieważ jest to klasa bazowa dla wszystkich innych klas). Dla przejrzystości kodu utworzyłem tylko 3 obiekty klas pochodnych i zapisałem wskaźniki na obiekty do poszczególnych indeksów tablicy. Dzięki użyciu polimorfizmu możemy zatrzymać wszystkie pojazdy w jednej pętli.

Tak jak pisałem wcześniej, o wywołaniu odpowiedniej przesłoniętej metody zadecydowało późne wiązanie. Uzyskaliśmy ten efekt dzięki zadeklarowaniu funkcji wirtualnej w klasie bazowej. A więc zadziałał polimorfizm. Gdybyśmy usunęli słowo virtual, trzy razy zostałaby wywołana funkcja klasy bazowej – zostałby trzy razy wyświetlony napis: zatrzymuje pojazd..?.

Przykład jest dość trywialny ale w chwili obecnej wydawał mi się najprostszy do ukazania zalet polimorfizmu a przede wszystkim jego praktycznej implementacji. Oczywiście wyobraź sobie sytuację, że w programie istnieje kilkaset obiektów a nie tylko trzy. Wtedy wygoda jaką zyskaliśmy jest nieoceniona.

Nie należy przesadzać

Polimorfizm kosztuje. Działa to w ten sam sposób jak deklaracja zmiennych statycznych i dynamicznych (stos jest szybszy od sterty). Gdy używamy polimorfizmu program aż do czasu uruchomienia nie wie jak będzie działał, ponieważ obiekt na jaki wskazuje wskaźnik może zmienić się 10 razy w ciągu minuty, zależnie od działania użytkownika.

Klasy polimorficzne zajmują więcej miejsca w pamięci, ponieważ kompilator automatycznie dodaje do nich wskaźnik vptr wskazujący na tablicę vtab. Dla każdej klasy musi istnieć osobny wskaźnik i osobna tablica. Tablica jest generowana automatycznie i zawiera wskaźniki do funkcji, wygenerowane przez kompilator. Nie wiem na jakiej zasadzie odbywa się generowanie zawartości tablicy i nie umiem nic więcej na ten temat napisać.

Sama tablica w budowie nie przypomina niczego nadzwyczajnego. Jej podgląd umożliwia m.in. środowisko Visual Studio po dopisaniu do kompilatora odpowiednich parametrów (zostanie wtedy wygenerowana automatycznie w formie pliku tekstowego).

Traktuj ten artykuł jako wstęp, opisałem w nim związek pomiędzy metodami wirtualnymi a polimorfizmem. Jeżeli temat dalej Cię interesuje, zapraszam do bardziej szczegółowego wpisu przedstawiającego Polimorfizm w C#.

Udostępnij ten artykuł na fejsie lub zostaw komentarz!

Komentarze:

Użytkownik Szymon napisał/a:

01 kwietnia 2014


Dobrze się czytało. :)

Użytkownik Maly napisał/a:

07 kwietnia 2014


Bardzo mi pomogło, dobrze napisane dla początkujących. Polecam :D

Użytkownik Grrr napisał/a:

05 czerwca 2014


Nie radzę czytać tego na kacu -.-

Użytkownik Kaśka napisał/a:

22 czerwca 2014


KAŻDY kod w arcie ma mem leak ;]

Użytkownik Kaczus napisał/a:

24 czerwca 2014


„W internecie często używana jest nazwa funkcji wirtualnej, jednak jest ona dość mylna i nie do końca zgodna z konwencją programowania obiektowego. Funkcja wirtualna musi być funkcją składową danej klasy, a więc generalnie jest metodą. Zwykłej funkcji nie da się zadeklarować jako wirtualna.”

Takie małe wyjaśnienie, nazwa metoda to w sumie nowsza wersja, na początku jak uczyłem się w zamierzchłych czasach języka C++ były funkcje składowe. Podział nazewnictwa, to czasy późniejsze, stąd pomieszanie w nazewnictwie.

Tak samo jak dawniej w okach nie było kontrolek, a gadżety :)

Użytkownik Kaczus napisał/a:

24 czerwca 2014


„Klasy polimorficzne zajmują więcej miejsca w pamięci, ponieważ kompilator automatycznie dodaje do nich wskaźnik vptr wskazujący na tablicę vtab. Dla każdej klasy musi istnieć osobny wskaźnik i osobna tablica. Tablica jest generowana automatycznie i zawiera wskaźniki do funkcji, wygenerowane przez kompilator. Nie wiem na jakiej zasadzie odbywa się generowanie zawartości tablicy i nie umiem nic więcej na ten temat napisać.”

Działa to w podobny sposób, w jakim działa polimorfizm np w języku C (opisałem to na mojej stronie http://kaczus.ppa.pl/art/C_obiektowo_cz4,9.html ) po prostu dla takiego obiektu – w zależności od jego typu odpowiednie wskaźniki funkcji są przypisywane przy tworzeniu obiektu odpowiedniej klasy. Przy okazji – ktoś już zwrócił uwagę – te przykłady mają nieładne memoryleaki, może warto to poprawić?.

Użytkownik Daniel napisał/a:

24 kwietnia 2015


Dzięki!

Przeczytałem chyba z tuzin stron, i nie mogłem zrozumieć stosowania, dzięki Twoim przykładom teraz wszystko jest jasne! :)

Użytkownik Domeny napisał/a:

24 czerwca 2015


Kilka interesujących przykładów zastosowania polimorfizmu. Bardzo fajny wpis. Pozdrawiam!

Zachęcam Cię do zostawienia komentarza!

Ilość znaków: 0

Zachęcam Cię do polubienia bloga na facebooku! Dając lajka wspierasz moją pracę - wszystkie artykuły na blogu są za darmo!