Odpowiedzi:
Jest to jeszcze ważniejsze w przypadku interfejsu. Każdy użytkownik twojej klasy prawdopodobnie będzie trzymał wskaźnik do interfejsu, a nie wskaźnik do konkretnej implementacji. Kiedy przyjdą, aby go usunąć, jeśli destruktor nie jest wirtualny, będą wywoływać destruktor interfejsu (lub wartość domyślną dostarczoną przez kompilator, jeśli go nie określono), a nie destruktor klasy pochodnej. Natychmiastowy wyciek pamięci.
Na przykład
class Interface
{
virtual void doSomething() = 0;
};
class Derived : public Interface
{
Derived();
~Derived()
{
// Do some important cleanup...
}
};
void myFunc(void)
{
Interface* p = new Derived();
// The behaviour of the next line is undefined. It probably
// calls Interface::~Interface, not Derived::~Derived
delete p;
}
[expr.delete]/
: ... if the static type of the object to be deleted is different from its dynamic type, ... the static type shall have a virtual destructor or the behavior is undefined. ...
. Byłoby nadal niezdefiniowane, gdyby funkcja Derived używała niejawnie wygenerowanego destruktora.
Odpowiedź na twoje pytanie brzmi często, ale nie zawsze. Jeśli twoja klasa abstrakcyjna zabrania klientom wywoływania funkcji delete na wskaźniku do niej (lub jeśli tak mówi w swojej dokumentacji), możesz nie deklarować wirtualnego destruktora.
Możesz zabronić klientom wywoływania funkcji delete na wskaźniku do niego, chroniąc jego destruktor. Działając w ten sposób, pominięcie wirtualnego destruktora jest całkowicie bezpieczne i rozsądne.
Ostatecznie skończysz bez wirtualnej tabeli metod i w końcu zasygnalizujesz swoim klientom zamiar uczynienia jej niemożliwą do usunięcia przez wskaźnik do niej, więc naprawdę masz powód, aby nie deklarować jej jako wirtualnej w takich przypadkach.
[Patrz punkt 4 w tym artykule: http://www.gotw.ca/publications/mill18.htm ]
Postanowiłem poszukać informacji i podsumować Twoje odpowiedzi. Poniższe pytania pomogą Ci zdecydować, jakiego rodzaju destruktora potrzebujesz:
Mam nadzieję, że to pomoże.
* Ważne jest, aby pamiętać, że w C ++ nie ma sposobu, aby oznaczyć klasę jako ostateczną (tj. Nie można jej podklasować), więc w przypadku, gdy zdecydujesz się zadeklarować swój destruktor jako niewirtualny i publiczny, pamiętaj, aby wyraźnie ostrzec innych programistów przed wywodzący się z twojej klasy.
Bibliografia:
Tak, to jest zawsze ważne. Klasy pochodne mogą przydzielać pamięć lub przechowywać odwołania do innych zasobów, które będą musiały zostać wyczyszczone, gdy obiekt zostanie zniszczony. Jeśli nie udostępnisz swoim interfejsom / klasom abstrakcyjnym wirtualnych destruktorów, to za każdym razem, gdy usuniesz instancję klasy pochodnej za pośrednictwem uchwytu klasy bazowej, destruktor klasy pochodnej nie zostanie wywołany.
Dlatego otwierasz potencjał wycieków pamięci
class IFoo
{
public:
virtual void DoFoo() = 0;
};
class Bar : public IFoo
{
char* dooby = NULL;
public:
virtual void DoFoo() { dooby = new char[10]; }
void ~Bar() { delete [] dooby; }
};
IFoo* baz = new Bar();
baz->DoFoo();
delete baz; // memory leak - dooby isn't deleted
Nie zawsze jest to wymagane, ale uważam, że jest to dobra praktyka. To, co robi, to umożliwia bezpieczne usunięcie obiektu pochodnego za pomocą wskaźnika typu podstawowego.
Na przykład:
Base *p = new Derived;
// use p as you see fit
delete p;
jest źle sformułowany, jeśli Base
nie ma wirtualnego destruktora, ponieważ będzie próbował usunąć obiekt tak, jakby był plikiem Base *
.
shared_ptr
będą próbowały usunąć obiekt tak, jakby był Base *
- pamięta typ rzeczy, za pomocą której go utworzyłeś. Zobacz odnośnik, do którego się odwołuje, w szczególności bit, który mówi: „Destruktor wywoła funkcję delete z tym samym wskaźnikiem, wraz z oryginalnym typem, nawet jeśli T nie ma wirtualnego destruktora lub jest pusty”.
To nie tylko dobra praktyka. Jest to zasada nr 1 dla dowolnej hierarchii klas.
A teraz dlaczego. Weźmy typową hierarchię zwierząt. Wirtualne destruktory przechodzą przez wirtualną wysyłkę, tak jak każde inne wywołanie metody. Weźmy następujący przykład.
Animal* pAnimal = GetAnimal();
delete pAnimal;
Załóżmy, że Animal jest klasą abstrakcyjną. Jedynym sposobem, w jaki C ++ zna właściwy destruktor do wywołania, jest wysłanie metody wirtualnej. Jeśli destruktor nie jest wirtualny, po prostu wywoła destruktor Animal i nie zniszczy żadnych obiektów w klasach pochodnych.
Powodem uczynienia destruktora wirtualnym w klasie bazowej jest to, że po prostu usuwa on wybór z klas pochodnych. Ich destruktor domyślnie staje się wirtualny.
Odpowiedź jest prosta, potrzebujesz, aby była wirtualna, w przeciwnym razie klasa bazowa nie byłaby pełną klasą polimorficzną.
Base *ptr = new Derived();
delete ptr; // Here the call order of destructors: first Derived then Base.
Wolisz powyższe usunięcie, ale jeśli destruktor klasy bazowej nie jest wirtualny, zostanie wywołany tylko destruktor klasy bazowej, a wszystkie dane w klasie pochodnej pozostaną nieusunięte.
delete p
wywołuje niezdefiniowane zachowanie. Nie ma gwarancji, że zadzwoniszInterface::~Interface
.