Dobrze rozumiem większość teorii OO, ale jedną z rzeczy, która mnie bardzo dezorientuje, są wirtualne niszczyciele.
Myślałem, że destruktor zawsze jest wywoływany bez względu na wszystko i dla każdego obiektu w łańcuchu.
Kiedy masz je uczynić wirtualnymi i dlaczego?
virtual
upewnia się, że zaczyna się na górze zamiast na środku.Odpowiedzi:
Wirtualne niszczyciele są przydatne, gdy potencjalnie możesz usunąć instancję klasy pochodnej za pomocą wskaźnika do klasy bazowej:
Tutaj zauważysz, że nie zadeklarowałem, że to Destruktor Bazy
virtual
. Teraz spójrzmy na następujący fragment:Od destructor baza nie jest
virtual
ib
jestBase*
skierowana naDerived
przedmiot,delete b
ma niezdefiniowanej zachowanie :W większości implementacji wywołanie destruktora zostanie rozwiązane tak jak każdy kod niewirtualny, co oznacza, że wywołany zostanie destruktor klasy bazowej, ale nie klasy pochodnej, co spowoduje wyciek zasobów.
Podsumowując, zawsze twórz niszczyciele klas podstawowych,
virtual
gdy mają być manipulowane polimorficznie.Jeśli chcesz zapobiec usunięciu instancji za pomocą wskaźnika klasy bazowej, możesz sprawić, że niszczyciel klasy bazowej będzie chroniony i niewirusowy; w ten sposób kompilator nie pozwoli ci wywołać
delete
wskaźnika klasy bazowej.Możesz dowiedzieć się więcej na temat wirtualności i wirtualnego niszczyciela klasy bazowej w tym artykule z Herb Sutter .
źródło
Base
iDerived
mieć wszystkie automatycznych zmiennych przechowywania? tzn. nie ma „specjalnego” ani dodatkowego niestandardowego kodu do wykonania w destruktorze. Czy można w ogóle przestać pisać jakiekolwiek niszczyciele? A może klasa pochodna nadal będzie miała przeciek pamięci?Wirtualny konstruktor nie jest możliwy, ale wirtualny destruktor jest możliwy. Pozwól nam eksperymentować .......
Powyższy kod wyświetla następujące dane:
Konstrukcja obiektu pochodnego jest zgodna z zasadą konstrukcji, ale kiedy usuwamy wskaźnik „b” (wskaźnik bazowy), stwierdziliśmy, że wywoływany jest tylko podstawowy destruktor. Ale to nie może się zdarzyć. Aby zrobić odpowiednią rzecz, musimy uczynić bazę destruktora wirtualną. Zobaczmy teraz, co dzieje się w następujący sposób:
Dane wyjściowe zmieniły się następująco:
Tak więc zniszczenie wskaźnika bazowego (który przyjmuje przydział dla obiektu pochodnego!) Odbywa się zgodnie z zasadą zniszczenia, tj. Najpierw Pochodna, a następnie Baza. Z drugiej strony nie ma wirtualnego konstruktora.
źródło
Deklaracja wirtualnych destruktorów w polimorficznych klasach podstawowych. To jest pozycja 7 w Effective C ++ Scott Meyers . Meyers podsumowuje, że jeśli klasa ma jakąkolwiek funkcję wirtualną, powinna mieć wirtualny destruktor, a klasy nie zaprojektowane jako klasy podstawowe lub nieprzeznaczone do użycia polimorficznego nie powinny deklarować wirtualnych destruktorów.
źródło
const Base& = make_Derived();
. W takim przypadkuDerived
zostanie wywołany destruktor wartości , nawet jeśli nie jest wirtualny, więc oszczędza się narzuty wprowadzone przez vtables / vpointers. Oczywiście zakres jest dość ograniczony. Andrei Alexandrescu wspomniał o tym w swojej książce Modern C ++ Design .Należy również pamiętać, że usunięcie wskaźnika klasy bazowej, gdy nie ma wirtualnego destruktora, spowoduje niezdefiniowane zachowanie . Czego nauczyłem się niedawno:
Jak powinno się zachowywać przesłonięcie usuwania w C ++?
Używam C ++ od lat i nadal potrafię się zawiesić.
źródło
Uaktywnij destruktor wirtualny, ilekroć Twoja klasa jest polimorficzna.
źródło
Wywoływanie destruktora za pomocą wskaźnika do klasy podstawowej
Wirtualne wywołanie destruktora nie różni się niczym od jakiegokolwiek wirtualnego wywołania funkcji.
Ponieważ
base->f()
wywołanie zostanie wysłane doDerived::f()
, i to samo dotyczybase->~Base()
- jego funkcji zastępującej -Derived::~Derived()
zostanie wywołane.To samo dzieje się, gdy destruktor jest wywoływany pośrednio, np
delete base;
.delete
Wezwanie oświadczenie wolibase->~Base()
, które będą wysyłane doDerived::~Derived()
.Klasa abstrakcyjna z nie-wirtualnym destruktorem
Jeśli nie zamierzasz usuwać obiektu przez wskaźnik do jego klasy bazowej - nie ma potrzeby używania wirtualnego destruktora. Po prostu zrób to,
protected
aby nie zostało przypadkowo nazwane:źródło
~Derived()
we wszystkich klasach pochodnych, nawet jeśli jest to po prostu~Derived() = default
? Czy jest to sugerowane przez język (dzięki czemu można go bezpiecznie pominąć)?protected
sekcję lub upewnić się, że jest wirtualna za pomocąoverride
.Lubię myśleć o interfejsach i implementacjach interfejsów. W C ++ interfejs jest czystą klasą wirtualną. Destructor jest częścią interfejsu i powinien zostać zaimplementowany. Dlatego destruktor powinien być czysto wirtualny. A może konstruktor? Konstruktor tak naprawdę nie jest częścią interfejsu, ponieważ obiekt jest zawsze tworzony jawnie.
źródło
virtual
w klasie bazowej, to automatycznie znajdzie sięvirtual
w klasie pochodnej, nawet jeśli nie zostanie zadeklarowany.Wirtualne słowo kluczowe dla destruktora jest konieczne, jeśli chcesz, aby różne destruktory zachowywały odpowiednią kolejność podczas usuwania obiektów przez wskaźnik klasy bazowej. na przykład:
Jeśli Twój destruktor klasy podstawowej jest wirtualny, obiekty zostaną zniszczone w kolejności (najpierw obiekt pochodny, a następnie baza). Jeśli Twój niszczyciel klasy bazowej NIE jest wirtualny, tylko obiekt klasy podstawowej zostanie usunięty (ponieważ wskaźnik ma klasę podstawową „Base * myObj”). Zatem nastąpi wyciek pamięci dla obiektu pochodnego.
źródło
Mówiąc prościej, Virtual destructor polega na niszczeniu zasobów w odpowiedniej kolejności po usunięciu wskaźnika klasy bazowej wskazującego na obiekt klasy pochodnej.
źródło
delete
podstawowego wskaźnika prowadzi do nieokreślonego zachowania.Destruktory wirtualnej klasy bazowej są „najlepszą praktyką” - zawsze należy ich używać, aby uniknąć (trudnych do wykrycia) wycieków pamięci. Korzystając z nich, możesz być pewien, że wszystkie destruktory w łańcuchu dziedziczenia twoich klas są wywoływane (w odpowiedniej kolejności). Dziedziczenie z klasy podstawowej za pomocą wirtualnego destruktora powoduje, że destruktor klasy dziedziczącej jest również automatycznie wirtualny (więc nie trzeba wpisywać „wirtualnego” w deklaracji dziedziczącego klasy destruktor).
źródło
Jeśli używasz
shared_ptr
(tylko shared_ptr, a nie unique_ptr), nie musisz mieć wirtualnego destruktora klasy podstawowej:wynik:
źródło
virtual
słowo kluczowe może uratować cię od wielu cierpień.Co to jest wirtualny destruktor lub jak korzystać z wirtualnego destruktora
Destruktor klas to funkcja o tej samej nazwie co klasa poprzedzająca ~, która ponownie przydzieli pamięć przydzieloną przez klasę. Dlaczego potrzebujemy wirtualnego destruktora
Zobacz poniższy przykład z niektórymi funkcjami wirtualnymi
Próbka pokazuje również, jak przekonwertować literę na górną lub dolną
Z powyższej próbki widać, że destruktor zarówno dla klasy MakeUpper, jak i MakeLower nie jest wywoływany.
Zobacz następną próbkę z wirtualnym destruktorem
Wirtualny destruktor wywoła jawnie najbardziej pochodny destruktor czasu wykonywania klasy, aby był w stanie wyczyścić obiekt we właściwy sposób.
Lub odwiedź link
https://web.archive.org/web/20130822173509/http://www.programminggallery.com/article_details.php?article_id=138
źródło
kiedy trzeba wywołać destruktor klasy pochodnej z klasy podstawowej. musisz zadeklarować wirtualny destruktor klasy bazowej w klasie bazowej.
źródło
Myślę, że sedno tego pytania dotyczy wirtualnych metod i polimorfizmu, a nie tylko destruktora. Oto wyraźniejszy przykład:
Wydrukuje:
Bez
virtual
tego wydrukuje:A teraz powinieneś zrozumieć, kiedy używać wirtualnych niszczycieli.
źródło
B b{}; A& a{b}; a.foo();
. SprawdzanieNULL
- które powinno byćnullptr
- przed wejściemdelete
- z niepoprawną motywacją - nie jest wymagane:delete nullptr;
jest zdefiniowane jako brak operacji . Jeśli już, powinieneś to sprawdzić przed rozmową telefoniczną->foo()
, w przeciwnym razie może wystąpić niezdefiniowane zachowanie, jeślinew
jakoś się nie powiedzie.)delete
naNULL
wskaźnik (czyli nie trzeba sięif (a != NULL)
strażnika).Pomyślałem, że korzystne byłoby omówienie „niezdefiniowanego” zachowania lub przynajmniej „awarii” niezdefiniowanego zachowania, które może wystąpić podczas usuwania przez klasę podstawową (/ struct) bez wirtualnego destruktora, a ściślej bez vtable. Poniższy kod zawiera kilka prostych struktur (to samo dotyczy klas).
Nie sugeruję, czy potrzebujesz wirtualnych niszczycieli, czy nie, choć ogólnie uważam, że dobrą praktyką jest ich posiadanie. Podaję tylko powód, dla którego możesz zakończyć się awarią, jeśli twoja klasa podstawowa (/ struct) nie ma vtable, a twoja klasa pochodna (/ struct) ma i usuwasz obiekt za pomocą klasy podstawowej (/ struct) wskaźnik. W takim przypadku adres, który przekazujesz wolnej procedurze stosu, jest nieprawidłowy, a zatem przyczyna awarii.
Jeśli uruchomisz powyższy kod, zobaczysz wyraźnie, kiedy wystąpi problem. Kiedy ten wskaźnik klasy bazowej (/ struct) różni się od tego wskaźnika klasy pochodnej (/ struct), napotkasz ten problem. W powyższej próbce struktury a i b nie mają vtables. Struktury C i D mają vtables. W ten sposób wskaźnik a lub b do instancji obiektu ac lub d zostanie ustalony, aby uwzględnić tabelę vtable. Jeśli przekażesz ten wskaźnik a lub b, aby go usunąć, nastąpi awaria z powodu nieprawidłowego adresu wolnej procedury sterty.
Jeśli planujesz usunąć instancje pochodne, które mają vtable ze wskaźników klas bazowych, musisz upewnić się, że klasa bazowa ma vtable. Jednym ze sposobów na to jest dodanie wirtualnego destruktora, który i tak może być potrzebny do prawidłowego wyczyszczenia zasobów.
źródło
Podstawowa definicja dotycząca
virtual
mówi, że określa, czy funkcja członka klasy może zostać zastąpiona w jej klasach pochodnych.D-tor klasy jest wywoływany w zasadzie na końcu zakresu, ale istnieje problem, na przykład, gdy definiujemy instancję na stercie (alokacja dynamiczna), powinniśmy ją usunąć ręcznie.
Jak tylko instrukcja zostanie wykonana, wywoływany jest destruktor klasy bazowej, ale nie dla pochodnej.
Praktycznym przykładem jest to, że w polu sterowania trzeba manipulować efektorami, siłownikami.
Na końcu zakresu, jeśli nie zostanie wywołany destruktor jednego z elementów mocy (siłownika), będą miały fatalne konsekwencje.
źródło
Każda klasa dziedziczona publicznie, polimorficzna lub nie, powinna mieć wirtualny destruktor. Innymi słowy, jeśli może to wskazywać wskaźnik klasy bazowej, jego klasa podstawowa powinna mieć wirtualny destruktor.
Jeśli jest wirtualny, wywoływany jest destruktor klasy pochodnej, a następnie konstruktor klasy podstawowej. Jeśli nie jest wirtualny, wywoływany jest tylko destruktor klasy podstawowej.
źródło