Historia pytania:
W C++ wprowadzono wsparcie dla programowania obiektowego, które jest podstawą współczesnych języków. Aby zrealizować polimorfizm, użyto funkcji wirtualnych. Umożliwiło to wywoływanie odpowiedniej implementacji metody w czasie wykonywania, a nie tylko kompilacji, co jest kluczowe dla architektury opartej na dziedziczeniu.
Problem:
Częstym błędem jest mylenie wywołań metod statycznych i dynamicznych, zapomniane wirtualne destruktory, niewłaściwe zarządzanie dziedziczeniem (na przykład, object slicing, wywołanie wersji bazowej zamiast nadpisanej). Często myli się, kiedy tak naprawdę działa polimorfizm.
Rozwiązanie:
Funkcja wirtualna jest deklarowana za pomocą słowa kluczowego virtual w klasie bazowej i może być nadpisana (override) w klasie pochodnej. Jeśli wywoła się funkcję przez wskaźnik lub referencję do klasy bazowej, zostanie wykonana wersja z klasy pochodnej.
Przykład kodu:
struct Base { virtual void foo() { std::cout << "Base::foo "; } }; struct Derived : Base { void foo() override { std::cout << "Derived::foo "; } }; void call(Base& b) { b.foo(); } int main() { Derived d; call(d); // Wyświetli Derived::foo }
Kluczowe cechy:
Czy polimorfizm działa przy przekazywaniu obiektu przez wartość?
Nie. Przekazywanie przez wartość prowadzi do "slicing" — kopiowana jest tylko część odpowiadająca typowi parametru (zwykle klasa bazowa), polimorfizm jest wyłączony.
Przykład kodu:
void call(Base b) { b.foo(); } // zawsze wywołanie Base::foo
Czy należy deklarować destruktor jako wirtualny w klasie bazowej?
Tak, jeśli przewiduje się usuwanie obiektów pochodnych przez wskaźnik do klasy bazowej. W przeciwnym razie dojdzie do wycieku pamięci lub niezamknięcia zasobów.
Przykład kodu:
struct Base { virtual ~Base() {} };
Co się stanie, jeśli nie użyje się słowa kluczowego override w klasie pochodnej?
Jeśli w klasie pochodnej nie oznaczono override, ale błędnie zmieniono sygnaturę funkcji (na przykład, pominięto const lub popełniono błąd w parametrach), funkcja nie nadpisze wirtualnej, tworzy się nowa, i polimorfizm nie działa jak oczekiwano.
Programista nie zdeklarował destruktora klasy bazowej jako wirtualny; usunięcie tablicy obiektów przez bazowy wskaźnik spowodowało wyciek pamięci.
Zalety:
Wady:
Zadeklarowano wirtualny destruktor; używano tylko referencji/wskaźników do typu bazowego. Polimorfizm działał poprawnie.
Zalety:
Wady: