Wiem, że final
słowo kluczowe jest używane, aby zapobiec zastąpieniu metody wirtualnej przez klasy pochodne. Nie mogę jednak znaleźć żadnego przydatnego przykładu, w którym powinienem naprawdę używać final
słowa kluczowego z virtual
metodą. Co więcej, wydaje się, że korzystanie final
z metod wirtualnych jest nieprzyjemnym zapachem, ponieważ uniemożliwia programistom rozszerzanie klasy w przyszłości.
Moje pytanie jest następne:
Czy jest jakiś użyteczny przypadek, kiedy naprawdę powinienem użyć final
w virtual
deklaracji metody?
Odpowiedzi:
Podsumowanie tego, co
final
robi słowo kluczowe: Załóżmy, że mamy klasę podstawowąA
i klasę pochodnąB
. Funkcjaf()
może być zadeklarowanaA
jako asvirtual
, co oznacza, że klasaB
może ją zastąpić. Ale wtedy klasaB
może życzyć sobie, aby jakakolwiek klasa, która jest pochodną dalej,B
nie była w stanie zastąpićf()
. Właśnie wtedy musimy zadeklarowaćf()
jakofinal
zaB
. Bezfinal
słowa kluczowego, gdy zdefiniujemy metodę jako wirtualną, dowolna klasa pochodna będzie mogła ją zastąpić. Słowofinal
kluczowe służy zakończeniu tej wolności.Przykładem dlaczego trzeba coś takiego: Załóżmy, że klasa
A
definiuje funkcję wirtualnąprepareEnvelope()
, a klasaB
zastępuje ją i wdrożył go jako sekwencji połączeń do własnych metod wirtualnychstuffEnvelope()
,lickEnvelope()
asealEnvelope()
. KlasaB
zamierza zezwolić klasom pochodnym na przesłonięcie tych wirtualnych metod w celu zapewnienia własnych implementacji, ale klasaB
nie chce pozwolić żadnej klasie pochodnej na przesłonięcie,prepareEnvelope()
a tym samym zmienić kolejność rzeczy, lizać, uszczelniać lub pomijać wywoływanie jednej z nich. Tak więc w tym przypadku klasaB
deklarujeprepareEnvelope()
jako ostateczną.źródło
class B might wish that any class which is further derived from B should not be able to override f()
? Czy istnieje przypadek w świecie rzeczywistym, w którym taka klasa B i metoda f () istnieją i dobrze pasują?Często jest użyteczne z punktu widzenia projektowania, aby móc oznaczyć rzeczy jako niezmienne. W ten sam sposób
const
zapewnia kompilator chroni i wskazuje, że stan nie powinien się zmienić,final
można użyć do wskazania, że zachowanie nie powinno się zmieniać dalej w dół hierarchii dziedziczenia.Przykład
Rozważ grę wideo, w której pojazdy zabierają gracza z jednego miejsca do drugiego. Wszystkie pojazdy powinny sprawdzić przed wyjazdem, czy podróżują do ważnej lokalizacji (upewniając się, że baza w tej lokalizacji nie jest zniszczona, np.). Możemy zacząć od użycia nie-wirtualnego idiomu interfejsu (NVI), aby zagwarantować, że ta kontrola zostanie przeprowadzona niezależnie od pojazdu.
Załóżmy teraz, że w naszej grze mamy latające pojazdy, a wszystkim , co wymaga i łączy wszystkie latające pojazdy, jest to, że przed startem muszą przejść kontrolę bezpieczeństwa w hangarze.
W tym miejscu możemy
final
zagwarantować, że wszystkie latające pojazdy przejdą taką kontrolę, a także poinformować o wymaganiach projektowych dotyczących latających pojazdów.Używając
final
w ten sposób, skutecznie rozszerzamy elastyczność nie-wirtualnego idiomu interfejsu, aby zapewnić jednolite zachowanie w hierarchii dziedziczenia (nawet po zastanowieniu się, przeciwdziałając delikatnemu problemowi z klasą bazową) na same funkcje wirtualne. Co więcej, kupujemy sobie wiggle miejsce, aby dokonać centralnych zmian, które wpływają na wszystkie typy latających pojazdów, bez modyfikowania każdej implementacji każdego latającego pojazdu.To jeden z takich przykładów użycia
final
. Istnieją konteksty, w których można się spotkać, w których nie ma sensu dalsze zastępowanie funkcji wirtualnego elementu członkowskiego - może to prowadzić do kruchego projektu i naruszenia wymagań projektowych.Jest
final
to przydatne z punktu widzenia projektowania / architektury.Jest to również przydatne z punktu widzenia optymalizatora, ponieważ dostarcza optymalizatorowi tych informacji projektowych, które umożliwiają dewiryzację wirtualnych wywołań funkcji (eliminując narzut dynamicznej wysyłki, a często bardziej znacząco, eliminując barierę optymalizacji między dzwoniącym a odbiorcą).
Pytanie
Z komentarzy:
Nie ma sensu, aby klasa podstawowa w katalogu głównym hierarchii deklarowała funkcję zarówno jako, jak
virtual
ifinal
. Wydaje mi się to dość głupie, ponieważ zmusiłoby to zarówno kompilatora, jak i czytelnika do przeskakiwania niepotrzebnych obręczy, których można uniknąć, po prostu unikającvirtual
w takim przypadku wprost. Jednak podklasy dziedziczą takie funkcje elementów wirtualnych:W tym przypadku, niezależnie od tego, czy
Bar::f
jawnie używa się wirtualnego słowa kluczowego,Bar::f
jest funkcją wirtualną. Słowovirtual
kluczowe staje się wówczas opcjonalne. Dlatego może być sensowneBar::f
określenie jakofinal
, nawet jeśli jest to funkcja wirtualna (final
może być używana tylko w przypadku funkcji wirtualnych).I niektórzy ludzie wolą, stylistycznie, jawnie wskazywać, że
Bar::f
jest wirtualny, tak jak:Dla mnie to trochę zbędne stosowanie obu
virtual
ifinal
specyfikatorów dla tej samej funkcji w tym kontekście (podobnievirtual
ioverride
), ale w tym przypadku jest to kwestia stylu. Niektóre osoby mogą uznać, żevirtual
przekazuje tutaj coś cennego, podobnie jak wextern
przypadku deklaracji funkcji z zewnętrznym powiązaniem (chociaż opcjonalnie nie ma innych kwalifikatorów powiązania).źródło
private
metodę wVehicle
klasie? Nie miałeś na myśliprotected
zamiast tego?private
nie obejmuje nadpisywania (nieco sprzeczne z intuicją). Specyfikacje publiczne / chronione / prywatne dla funkcji wirtualnych mają zastosowanie tylko do dzwoniących, a nie do nadpisujących, mówiąc krótko. Klasa pochodna może zastępować funkcje wirtualne z klasy bazowej niezależnie od jej widoczności.protected
może mieć nieco bardziej intuicyjny sens. Po prostu wolę sięgać po możliwie najlepszą widoczność, kiedy tylko mogę. Myślę, że powodem tego, że język został zaprojektowany w ten sposób, jest to, że w przeciwnym razie prywatne funkcje wirtualnych członków nie miałyby żadnego sensu poza kontekstem przyjaźni, ponieważ żadna klasa oprócz znajomego nie byłaby w stanie ich zastąpić, gdyby specyfikatory dostępu miały znaczenie w kontekście nadpisywania, a nie tylko dzwonienia.protected
aby uniknąć mylenia innych. Skończyło się na tym, że dodałem komentarz, opisujący, w jaki sposób prywatne funkcje wirtualne mogą być nadal zastępowane.Umożliwia wiele optymalizacji, ponieważ w czasie kompilacji można wiedzieć, która funkcja jest wywoływana.
Uważaj, rzucając słowo „zapach kodu”. „końcowy” nie uniemożliwia rozszerzenia klasy. Dwukrotnie kliknij „ostatnie” słowo, naciśnij backspace i przedłuż klasę. JEDNAK wersja jest doskonałą dokumentacją, że programista nie oczekuje, że zastąpisz funkcję, i dlatego też następny programista powinien być bardzo ostrożny, ponieważ klasa może przestać działać poprawnie, jeśli metoda końcowa zostanie w ten sposób nadpisana.
źródło
final
ivirtual
nigdy być używane w tym samym czasie?final
ioverride
.final
może naprawdę im pomóc