Aby zapewnić właściwe usuwanie obiektów, należy deklarować destruktor jako funkcję wirtualną, jeśli tylko mamy zamiar korzystać z publicznego dziedziczenia z danej klasy. Wywołując wtedy destruktor (poprzez operator delete) obiektu klasy pochodnej wskazywanego przez wskaźnik do klasy bazowej, wywołamy naprawdę destruktor dla całego obiektu. Dzięki polimorfizmowi wywołany będzie bowiem wtedy destruktor z klasy pochodnej, a destruktor podobiektu klasy bazowej będzie potem wywołany i tak, według normalnych zasad kolejności wywoływania destruktorów. Jak bowiem mówiliśmy, przy usuwaniu obiektu klasy pochodnej najpierw wykonywany jest destruktor dla części „własnej”, a potem automatycznie wywoływany jest destruktor dla podobiektu klasy bazowej. Gdyby destruktor nie był wirtualny, to ponieważ typem statycznym jest wskaźnik do obiektu klasy bazowej, wywołany byłby od razu destruktor z tej klasy, który jednak nie wie na przykład o istnieniu składowych czy zasobów dodanych w klasie pochodnej.
W poniższym przykładzie obiekt klasy pochodnej
Pelne
jest
wskazywany przez wskaźnik
osoba
typu
Nazwisko*, a
więc wskaźnik do obiektu klasy bazowej
Nazwisko.
1. #include <iostream> 2. using namespace std; 3. 4. class Nazwisko { 5. char* nazwis; 6. public: 7. Nazwisko(const char* n) 8. : nazwis(strcpy(new char[strlen(n)+1], n)) 9. { 10. cout << "Ctor Nazwisko: " << nazwis << endl; 11. } 12. 13. virtual 14. ~Nazwisko() { 15. cout << "Dtor Nazwisko: " << nazwis << endl; 16. delete [] nazwis; 17. } 18. }; 19. 20. class Pelne : public Nazwisko { 21. char* imie; 22. public: 23. Pelne(const char* i, const char* n) 24. : Nazwisko(n), 25. imie(strcpy(new char[strlen(i)+1], i)) 26. { 27. cout << "Ctor Pelne, Imie: " << imie << endl; 28. } 29. 30. ~Pelne() { 31. cout << "Dtor Pelne, Imie: " << imie << endl; 32. delete [] imie; 33. } 34. }; 35. 36. int main() { 37. Nazwisko* osoba = new Pelne("Jan", "Malinowski"); 38. delete osoba; 39. }
Ctor Nazwisko: Malinowski Ctor Pelne, Imie: Jan Dtor Pelne, Imie: Jan Dtor Nazwisko: MalinowskiGdyby destruktor w klasie bazowej Nazwisko nie był zadeklarowany jako wirtualny (po wykomentowaniu linii 13), to wywołany byłby tylko destruktor z klasy określanej przez statyczny typ wskaźnika, a więc z klasy Nazwisko:
Ctor Nazwisko: Malinowski Ctor Pelne, Imie: Jan Dtor Nazwisko: Malinowskii, jak widać, imię w ogóle nie zostałoby usunięte!
Zauważmy, że mamy tu do czynienia z pewną niekonsekwencją: destruktor w klasie pochodnej, ˜Full, przesłania destruktor z klasy bazowej, ˜Name, choć ich nazwy są inne. Pod tym względem destruktor jest wyjątkowy: w innych przypadkach metoda przesłaniająca musi oczywiście mieć tę samą nazwę co metoda przesłaniana.
T.R. Werner, 21 lutego 2016; 20:17