';

Wirtualne destruktory w C++

Wirtualne destruktory w C++

Destruktor klasy jest jego specjalną metodą składową, która wywoływana jest wraz z końcem życia obiektu. Przeznaczenie tej funkcji jest najczęściej odwrotne do przeznaczenia konstruktorów, a jest nim zwolnienie zasobów nabytych przez obiekt w trakcie jego cyklu życia.

Jak już wspominałem, destruktor wywoływany jest wraz z zakończeniem cyklu życia obiektu. A dokładniej, kiedy?
1. Dla obiektów statycznych, na zakończenie działaniu programu.
2. Dla obiektów lokalnych dla wątku – na zakończenie wątku.
3. Dla obiektów automatycznych i tymczasowych, których zasięg przedłużony został poprzez przypisanie do referencji, na koniec zasięgu.
4. Dla obiektów dynamicznie zaalokowanych – na wywołanie wyrażenia delete (ale nie free()!).
5. Dla nienazwanych obiektów tymczasowych – na koniec wyrażenia.
6. Dla obiektów automatycznych w przypadku wyrzucenia wyjątku – w trakcie odwijania stosu.

Działanie destruktorów nie jest skomplikowane. Jednakże, odgrywają one jeszcze jedną kluczową rolę w C++ – w klasach bazowych i pochodnych w tworzeniu hierarchii klas. Kiedy i jak pisać destruktory w dziedziczeniu? Nie jest to już tak oczywiste.

W C++, wirtualne destruktory są kluczową częścią definicji klas bazowych. Bez nich nie dochodzi do poprawnego zwolnienia zaalokowanej pamięci w przypadku użycia wyrażenia delete na wskaźniku na klasę bazową (czyli po prostu w przypadku polimorfizmu). A zatem każda klasa bazowa musi posiadać albo 1) destruktor wirtualny z definicją (nawet jeśli jest czysto wirtualny), albo 2) destruktor chroniony (ang. protected) i nie-wirtualny.
Dlaczego klasa bazowa wymaga pełnej definicji destruktora, nawet jeśli jest on czysto wirtualny? Otóż, jest on zawsze wywoływany w trakcie usuwania obiektów pochodnych – stąd też linker wymaga definicji tej funkcji.
Jeśli klasa nie posiada zatem ani destruktora wirtualnego, ani destruktora chronionego nie-wirtualnego, można uznać, że klasa nie została przeznaczona na bycie klasą bazową – stąd też równie dobrze można by ją oznaczyć słowem kluczowym final.

A co z klasami pochodnymi? Oto ciekawe zasady:
1. Jeśli przesłaniamy destruktor z klasy bazowej, pamiętajmy o wykorzystaniu słów kluczowych override i final. Od C++11 nie powinniśmy używać słowa virtual w kontekście przesłaniania metod w ogóle.
2. Jeśli nie ma potrzeby pisania destruktora w klasie pochodnej, to nie róbmy tego i pozwólmy kompilatorowi go wygenerować. Wystarczy, że klasa bazowa poprawnie definiuje wirtualny destruktor.

class BaseA
{
public:
    virtual ~BaseA() = default;
};

class DerivedA : public BaseA
{
public:
    ~DerivedA() override = default; // Not needed.
};

class DerivedB : public BaseA
{
public:
    ~DerivedB() override; // OK. Most probably 'somePtr' needs to be free'd.
private:
    int* somePtr;
};

Literatura:
1. https://en.cppreference.com/w/cpp/language/destructor
2. https://isocpp.github.io/CppCoreGuidelines/
3. https://www.autosar.org/fileadmin/user_upload/standards/adaptive/17-03/AUTOSAR_RS_CPP14Guidelines.pdf

Komentarzy
Udostępnij
Piotr Tański

Wszechstronny i doświadczony programista i projektant oprogramowania. Certyfikowany programista języka C, ekspert języka C++. Prywatnie - pasjonat nowoczesnych technologii i podróży. Zwiedził już wiele miejsc na świecie i ciągle mu mało! Poza tym, fan piłki nożnej, pieszych wycieczek i muzyki rockowej. Stara się też na bieżąco poszerzać wiedzę z historii.

Skomentuj

pl_PLPolski
en_GBEnglish (UK) pl_PLPolski