Jak działają metody wirtualne

2008-07-24 17:16

Zauważyłem, że niektóre osoby mają problem z właściwym wykorzystaniem metod wirtualnych i polimorfizmu, nie widząc czasami możliwości ich wykorzystania nawet w najbardziej oczywistych przypadkach. Być może powodem jest coś w rodzaju braku zaufania do całego mechanizmu, który jest spowodowany wrażeniem, że kompilator dokonuje przy jego okazji jakichś pseudomagicznych sztuczek.

A tak naprawdę z metodami wirtualnymi sprawa jest dosyć prosta. Pomiędzy nimi a zwykłymi metodami różnica polega wyłącznie na sposobie ich wywoływania.
W tym pierwszym przypadku użycie zwykłej metody względem obiektu:

  1. CObject* pObject = new CObject;
  2. // ...
  3. pObject->Method();

jest zamieniane w wynikowym kodzie na normalne wywołanie funkcji z dodatkowym parametrem:

  1. CObject_Method (pObject); // nazwa tej funkcji jest czysto umowna

Ten parametr jest potem dostępny jako wskaźnik this wewnątrz metody i pozwala odwoływać się do innych składowych obiektu.

W przypadku metod wirtualnych wywołanie jest natomiast dwuetapowe i polega na wykorzystaniu pewnych dodatkowych informacji. Są nimi: tablica funkcji wirtualnych (zwana vtable) oraz wskaźnik na nią (czyli vptr). Tablica występuje tylko w jednej kopii na całą klasę i zawiera tyle elementów, ile funkcji wirtualnych klasa ta posiada. Jej elementami są po prostu adresy w pamięci tych właśnie funkcji: pierwsza funkcja wirtualna ma więc adres zapisany w elemencie vtable o indeksie 0, druga – o indeksie 1, itd.


Tablica metod wirtualnych i wskaźnik na nią (Źródło)

Vptr jest natomiast daną trzymaną wraz z każdym obiektem, podobnie jak zwykłe pola tego obiektu (często vptr jest umieszczany w pamięci tuż po nich). Jest on niczym innym, jak wskaźnikiem na vtable i służy obiektowi, gdy ten chce wywołać którąś ze swoich metod wirtualnych. Dokładniej wygląda to mniej więcej tak:

  1. // (wywołanie pObject->Method(), jeśli Method() jest wirtualna)
  2. pMethod = pObject->_vptr[0]; // załóżmy, że metoda ta ma indeks 0
  3. (*pMethod)(pObject);

Widać więc, że polega to po prostu na pobraniu odpowiedniego adresu metody z vtable, kierując się indeksem ustalanym w czasie kompilacji, zwykle na podstawie kolejności deklaracji metod wirtualnych w bloku class.

Dlaczego jednak raz może być wywołana wersja metody z klasy bazowej, a raz z pochodnej?… Tego nietrudno się już chyba domyślić: wszystko zależy od tego, na jaką tablicę pokazuje vptr. Jego ustawienie następuje automatycznie w trakcie tworzenia obiektu; wtedy wiadomo oczywiście, jakiego on jest typu. Potem jednak może on być dostępny zarówno przez wskaźnik do swojej klasy, jak i do klasy bazowej. W obu przypadkach metody wirtualne będą działały poprawnie, gdyż w ich wywołaniu będzie pośredniczył vptr.

Tak to wszystko wygląda, w wielkim skrócie rzecz jasna :) Całość nie jest może trywialna ze względu na pewne kombinacje ze wskaźnikami do funkcji, jednak nie zaszkodzi mieć przynajmniej ogólne pojęcie o tym, jak to właściwie działa. Istnieje szansa, że dzięki temu będziemy potrafili korzystać nieco lepiej z metod wirtualnych, a przy okazji trochę więcej rozumieć z tajemniczych dyskusji “ekspertów C++” przerzucających się takimi tajemniczymi terminami jak vtable i vptr ;-]

Tags: ,
Author: Xion, posted under Programming »


5 comments for post “Jak działają metody wirtualne”.
  1. Anonymous:
    July 24th, 2008 o 17:31

    Dość sporo informacji na ten temat można znaleźć w Thinking in C++ vol. 1 jeśli mnie pamięć nie myli.

    p.s. Może źle kojarzę, ale nie wypowiadałeś się negatywnie na temat notacji węgierskiej jakiś czas temu w którejś z notek? ^.-

  2. Xion:
    July 24th, 2008 o 17:42

    Akurat w C++ odróżnianie wskaźnika od obiektu/referencji jest dość ważne (weźmy -> vs kropka), więc przedrostek ‘p’ to taki drobny kompromis :)

  3. macabre13:
    July 25th, 2008 o 8:02

    Na codeproject jest bardzo obszerny art o metodach wirtualnych. Jezeli kogos to interesuje to polecam przejrzenie.

  4. I:
    July 25th, 2008 o 12:57

    Niestety ta notka nie wyczerpuje puli problemow zwiazanych z wywolywaniem funkcji klas w ktorych zastosowano dziedziczenie. Z doswiadczenia wiem, ze wiele osob ma z tym problemy :)

  5. agent_J:
    July 26th, 2008 o 12:43

    Ja używam czasami podmieniania i patchowania vtable do reversowania programów ;)

Comments are disabled.
 


© 2017 Karol Kuczmarski "Xion". Layout by Urszulka. Powered by WordPress with QuickLaTeX.com.