programowanieProgramista C++

Jak działa mechanizm funkcji wirtualnych i wirtualnego dziedziczenia w C++? Jakie są cechy projektowania klas z abstrakcyjnym interfejsem?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź

Funkcje wirtualne pozwalają na realizację polimorfizmu — możliwość przetwarzania obiektów klas pochodnych przez wskaźniki lub referencje do klasy bazowej. Do zadeklarowania funkcji wirtualnej używa się słowa kluczowego virtual:

class Base { public: virtual void foo() { std::cout << "Base::foo"; } }; class Derived : public Base { public: void foo() override { std::cout << "Derived::foo"; } };

Wywołanie foo() przez wskaźnik typu Base* wywołuje implementację w Derived, jeśli wskazuje na obiekt Derived.

Klasa abstrakcyjna — zawiera przynajmniej jedną czysto wirtualną funkcję:

class Interface { public: virtual void process() = 0; // czysto wirtualna funkcja };

Wirtualne dziedziczenie rozwiązuje problem diamentowego dziedziczenia (diamond problem):

class A { }; class B : virtual public A { }; class C : virtual public A { }; class D : public B, public C { };

Gwarantuje to, że w obiekcie D będzie jedyny egzemplarz klasy bazowej A.

Pytanie podchwytliwe

Jaka jest różnica między wirtualnym destruktorem a zwykłym? Czy należy robić destruktor wirtualnym, jeśli klasa jest przeznaczona do dziedziczenia?

Odpowiedź: Wirtualny destruktor gwarantuje, że przy usuwaniu przez wskaźnik typu bazowego zostanie wywołany destruktor samej klasy pochodnej. Jest to ważne dla poprawnego zwalniania zasobów.

class Base { public: virtual ~Base() {} };

Jeśli destruktor nie jest wirtualny, to przy delete BasePtr; w obiekcie klasy pochodnej zostanie wywołany tylko destruktor Base, a zasoby odziedziczonych pól nie zostaną zwolnione.

Przykłady rzeczywistych błędów z powodu braku wiedzy na temat szczegółów tematu


Historia

W dużym systemie finansowym, gdzie dla różnych narzędzi istniał wspólny interfejs klasy, nie zadeklarowano wirtualnego destruktora. W jednym z dziedziców używano dynamicznego zasobu. Przy usuwaniu obiektu przez wskaźnik na typ bazowy wystąpił wyciek pamięci, który znaleziono dopiero na etapie obciążeń przemysłowych.


Historia

Zespół używał wielokrotnego dziedziczenia, nie stosując wirtualnego dziedziczenia. Klasa D dziedziczyła dwa razy A przez klasy pośrednie. Doprowadziło to do duplikacji stanu i błędów przy odwoływaniu się do członków A. Naprawiono to dopiero po audycie za pomocą narzędzi analizy statycznej.


Historia

W projekcie rozwoju wtyczek do rejestrowania używano klasy abstrakcyjnej, ale nie zrobiono jej destruktora wirtualnym. Przy usuwaniu wtyczek przez wskaźnik interfejsu występowały nieoczywiste zależności i błędy związane z niewywoływanymi destruktorami potomków. Problem obejmował pulę zasobów i prowadził do wycieków zasobów.