logo

Funkcja wirtualna w C++

Funkcja wirtualna (znana również jako metody wirtualne) to funkcja składowa zadeklarowana w klasie bazowej i ponownie zdefiniowana (przesłonięta) przez klasę pochodną. Kiedy odwołujesz się do obiektu klasy pochodnej za pomocą wskaźnika lub odwołania do klasy bazowej, możesz wywołać funkcję wirtualną dla tego obiektu i wykonać wersję metody klasy pochodnej.

  • Funkcje wirtualne zapewniają, że dla obiektu zostanie wywołana właściwa funkcja, niezależnie od typu referencji (lub wskaźnika) użytego do wywołania funkcji.
  • Stosowane są głównie w celu osiągnięcia polimorfizmu środowiska wykonawczego.
  • Funkcje deklaruje się za pomocą a wirtualny słowo kluczowe w klasie bazowej.
  • Rozwiązywanie wywołań funkcji odbywa się w czasie wykonywania.

Reguły funkcji wirtualnych

Reguły funkcji wirtualnych w C++ są następujące:



  1. Funkcje wirtualne nie mogą być statyczne.
  2. Funkcja wirtualna może być funkcją zaprzyjaźnioną innej klasy.
  3. Dostęp do funkcji wirtualnych należy uzyskać za pomocą wskaźnika lub referencji typu klasy bazowej, aby osiągnąć polimorfizm środowiska wykonawczego.
  4. Prototyp funkcji wirtualnych powinien być taki sam w klasie bazowej i pochodnej.
  5. Są one zawsze definiowane w klasie bazowej i zastępowane w klasie pochodnej. Przesłonięcie (lub ponowne zdefiniowanie funkcji wirtualnej) przez klasę pochodną nie jest obowiązkowe; w takim przypadku używana jest wersja funkcji klasy bazowej.
  6. Klasa może mieć wirtualny destruktor, ale nie może mieć wirtualnego konstruktora.

Czas kompilacji (wczesne wiązanie) VS zachowanie funkcji wirtualnych w czasie wykonywania (późne wiązanie).

Rozważmy następujący prosty program pokazujący zachowanie funkcji wirtualnych w czasie wykonywania.

Znak ciągu Java

C++






przeciążanie metody



// C++ program to illustrate> // concept of Virtual Functions> #include> using> namespace> std;> class> base {> public>:> >virtual> void> print() { cout <<>'print base class '>; }> >void> show() { cout <<>'show base class '>; }> };> class> derived :>public> base {> public>:> >void> print() { cout <<>'print derived class '>; }> >void> show() { cout <<>'show derived class '>; }> };> int> main()> {> >base* bptr;> >derived d;> >bptr = &d;> >// Virtual function, binded at runtime> >bptr->drukuj();> >// Non-virtual function, binded at compile time> >bptr->pokaż();> >return> 0;> }>

>

>

Wyjście

print derived class show base class>

Wyjaśnienie: Polimorfizm środowiska wykonawczego osiąga się jedynie poprzez wskaźnik (lub odwołanie) typu klasy bazowej. Ponadto wskaźnik klasy bazowej może wskazywać na obiekty klasy bazowej, a także na obiekty klasy pochodnej. W powyższym kodzie wskaźnik klasy bazowej „bptr” zawiera adres obiektu „d” klasy pochodnej.

Późne wiązanie (czas wykonywania) odbywa się zgodnie z zawartością wskaźnika (tj. lokalizacją wskazywaną przez wskaźnik), a wczesne wiązanie (czas kompilacji) odbywa się zgodnie z typem wskaźnika, ponieważ funkcja print() jest zadeklarowana za pomocą wirtualnego słowo kluczowe, więc zostanie powiązane w czasie wykonywania (wyjście to klasa pochodna wydruku ponieważ wskaźnik wskazuje obiekt klasy pochodnej), a show() nie jest wirtualne, więc zostanie powiązane w czasie kompilacji (wyjście to pokaż klasę bazową ponieważ wskaźnik jest typu podstawowego).

kółko przewijania nie działa

Notatka: Jeśli utworzyliśmy funkcję wirtualną w klasie bazowej i jest ona nadpisywana w klasie pochodnej, to nie potrzebujemy słowa kluczowego virtual w klasie pochodnej, funkcje są automatycznie uznawane za funkcje wirtualne w klasie pochodnej.

Działanie funkcji wirtualnych (koncepcja VTABLE i VPTR)

Jak omówiono tutaj, jeśli klasa zawiera funkcję wirtualną, kompilator sam robi dwie rzeczy.

  1. Jeśli zostanie utworzony obiekt tej klasy, wówczas a wskaźnik wirtualny (VPTR) jest wstawiany jako element danych klasy, aby wskazać VTABLE tej klasy. Dla każdego nowo utworzonego obiektu wstawiany jest nowy wirtualny wskaźnik jako element danych tej klasy.
  2. Niezależnie od tego, czy obiekt został utworzony, czy nie, klasa zawiera jako element członkowski statyczna tablica wskaźników funkcji zwana VTABLE . Komórki tej tabeli przechowują adres każdej funkcji wirtualnej zawartej w tej klasie.

Rozważ poniższy przykład:

wirtualny wskaźnik i wirtualna tabela

rdzeń języka Java

C++




// C++ program to illustrate> // working of Virtual Functions> #include> using> namespace> std;> class> base {> public>:> >void> fun_1() { cout <<>'base-1 '>; }> >virtual> void> fun_2() { cout <<>'base-2 '>; }> >virtual> void> fun_3() { cout <<>'base-3 '>; }> >virtual> void> fun_4() { cout <<>'base-4 '>; }> };> class> derived :>public> base {> public>:> >void> fun_1() { cout <<>'derived-1 '>; }> >void> fun_2() { cout <<>'derived-2 '>; }> >void> fun_4(>int> x) { cout <<>'derived-4 '>; }> };> int> main()> {> >base* p;> >derived obj1;> >p = &obj1;> >// Early binding because fun1() is non-virtual> >// in base> >p->zabawa_1();> >// Late binding (RTP)> >p->zabawa_2();> >// Late binding (RTP)> >p->zabawa_3();> >// Late binding (RTP)> >p->zabawa_4();> >// Early binding but this function call is> >// illegal (produces error) because pointer> >// is of base type and function is of> >// derived class> >// p->zabawa_4(5);> >return> 0;> }>

data lokalna
>

>

Wyjście

base-1 derived-2 base-3 base-4>

Wyjaśnienie: Początkowo tworzymy wskaźnik typu klasa bazowa i inicjujemy go adresem obiektu klasy pochodnej. Kiedy tworzymy obiekt klasy pochodnej, kompilator tworzy wskaźnik jako element danych klasy zawierający adres VTABLE klasy pochodnej.

Podobna koncepcja Późne i wczesne wiązanie jest używany jak w powyższym przykładzie. W przypadku wywołania funkcji fun_1() wywoływana jest wersja funkcji w klasie bazowej, fun_2() jest zastępowana w klasie pochodnej, więc wywoływana jest wersja klasy pochodnej, fun_3() nie jest zastępowana w klasie pochodnej i jest funkcją wirtualną więc wywoływana jest wersja klasy bazowej, podobnie fun_4() nie jest zastępowana, więc wywoływana jest wersja klasy bazowej.

Notatka: fun_4(int) w klasie pochodnej różni się od funkcji wirtualnej fun_4() w klasie bazowej, ponieważ prototypy obu funkcji są różne.

Ograniczenia funkcji wirtualnych

    Wolniej: Wywołanie funkcji trwa nieco dłużej ze względu na mechanizm wirtualny i utrudnia optymalizację kompilatorowi, ponieważ nie wie on dokładnie, która funkcja zostanie wywołana w czasie kompilacji. Trudne do debugowania: w złożonym systemie funkcje wirtualne mogą nieco utrudnić ustalenie, skąd funkcja jest wywoływana.