Dziedzictwo
Głównym celem dziedziczenia jest współdzielenie wspólnego interfejsu i protokołu między wieloma różnymi implementacjami, tak że instancja klasy pochodnej może być traktowana identycznie jak każda inna instancja z dowolnego innego typu pochodnego.
W C ++ dziedziczenie niesie ze sobą również szczegóły implementacji, oznaczenie (lub brak oznaczenia) destruktora jako wirtualnego jest jednym z takich szczegółów implementacji.
Wiązanie funkcji
Teraz, gdy wywoływana jest funkcja lub którykolwiek z jej specjalnych przypadków, takich jak konstruktor lub destruktor, kompilator musi wybrać, która implementacja funkcji była przeznaczona. Następnie musi wygenerować kod maszynowy zgodny z tą intencją.
Najprostszym sposobem jest wybranie funkcji w czasie kompilacji i wysłanie wystarczającej ilości kodu maszynowego, aby niezależnie od jakichkolwiek wartości, podczas wykonywania tego fragmentu kodu, zawsze uruchamiał kod funkcji. Działa to świetnie, z wyjątkiem dziedziczenia.
Jeśli mamy klasę podstawową z funkcją (może to być dowolna funkcja, w tym konstruktor lub destruktor), a kod wywołuje na niej funkcję, co to oznacza?
Biorąc z twojego przykładu, jeśli initialize_vector()
wywołałeś kompilator, musisz zdecydować, czy naprawdę chciałeś wywołać implementację znalezioną w Base
lub implementację znalezioną w Derived
. Istnieją dwa sposoby, aby to ustalić:
- Pierwszą jest decyzja, że ponieważ zadzwoniłeś z
Base
typu, miałeś na myśli implementację w Base
.
- Drugi polega na podjęciu decyzji, ponieważ ponieważ typem wykonawczym wartości przechowywanej w
Base
wpisanej wartości może być Base
, lub Derived
że decyzja o tym, które wezwanie do wykonania, musi zostać podjęta w czasie wykonywania po wywołaniu (za każdym razem, gdy jest wywoływana).
Kompilator w tym momencie jest zdezorientowany, obie opcje są jednakowo prawidłowe. To jest, kiedy virtual
wchodzi w skład miksu. Gdy to słowo kluczowe jest obecne, kompilator wybiera opcję 2 opóźniającą decyzję między wszystkimi możliwymi implementacjami, aż kod będzie działał z rzeczywistą wartością. W przypadku braku tego słowa kluczowego kompilator wybiera opcję 1, ponieważ jest to normalne zachowanie.
Kompilator może nadal wybrać opcję 1 w przypadku wirtualnego wywołania funkcji. Ale tylko jeśli może udowodnić, że tak jest zawsze.
Konstruktory i niszczyciele
Dlaczego więc nie określamy wirtualnego konstruktora?
Bardziej intuicyjnie, w jaki sposób kompilator wybierałby między identycznymi implementacjami konstruktora dla Derived
i Derived2
? To jest dość proste, nie może. Nie ma żadnej wcześniejszej wartości, na podstawie której kompilator mógłby dowiedzieć się, co naprawdę było zamierzone. Nie ma wcześniejszej wartości, ponieważ jest to zadanie konstruktora.
Dlaczego więc musimy określić wirtualny destruktor?
Bardziej intuicyjnie, w jaki sposób kompilator wybierałby implementacje dla Base
i Derived
? Są to tylko wywołania funkcji, więc zachowuje się zachowanie wywołania funkcji. Bez deklarowanego wirtualnego destruktora kompilator zdecyduje się na bezpośrednie powiązanie z Base
destruktorem, niezależnie od typu środowiska uruchomieniowego wartości.
W wielu kompilatorach, jeśli pochodne nie deklarują żadnych elementów danych ani nie dziedziczą po innych typach, zachowanie w ~Base()
będzie odpowiednie, ale nie jest gwarantowane. Działałoby to wyłącznie przypadkowo, podobnie jak stanie przed miotaczem ognia, który nie został jeszcze zapalony. Przez jakiś czas nic ci nie jest.
Jedynym prawidłowym sposobem zadeklarowania dowolnego typu bazowego lub interfejsu w C ++ jest zadeklarowanie wirtualnego destruktora, aby wywołać prawidłowy destruktor dla dowolnej instancji hierarchii typów tego typu. Dzięki temu funkcja z największą wiedzą o instancji może poprawnie wyczyścić tę instancję.
~derived()
delegację do destruktora vec. Alternatywnie zakładasz,unique_ptr<base> pt
że poznasz pochodną destruktor. Bez metody wirtualnej tak nie jest. Podczas gdy unikatowi można przypisać funkcję usuwania, która jest parametrem szablonu bez reprezentacji środowiska wykonawczego, a funkcja ta nie ma zastosowania w tym kodzie.