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: Malinowski
Gdyby 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: Malinowski
i, 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