Jaki jest cel final
słowa kluczowego w C ++ 11 dla funkcji? Rozumiem, że zapobiega to zastępowaniu funkcji przez klasy pochodne, ale jeśli tak jest, czy nie wystarczy zadeklarować funkcje niewirtualne jako niewirtualne final
? Czy jest coś, czego tu brakuje?
143
virtual
słowa kluczowego, czy nie.func
nie jest wirtualny, więc nie ma nic do zastąpienia, a zatem nie ma nic do oznaczenia jakooverride
lubfinal
.Odpowiedzi:
To, czego ci brakuje, jak idljarn już wspomniano w komentarzu, to fakt, że jeśli nadpisujesz funkcję z klasy bazowej, nie możesz oznaczyć jej jako niewirtualnej:
źródło
virtual
może powodować błędy, a C ++ 11 dodałoverride
znacznik do funkcji, która wykryje tę sytuację i nie uda się skompilować, gdy funkcja, która ma zastąpić, faktycznie się ukrywaMa to na celu zapobieganie dziedziczeniu klasy. Z Wikipedii :
Służy również do oznaczania funkcji wirtualnej, aby zapobiec jej przesłonięciu w klasach pochodnych:
Wikipedia przedstawia ponadto interesujący punkt :
Oznacza to, że dozwolone są następujące czynności:
źródło
„final” pozwala również optymalizacji kompilatora na ominięcie wywołania pośredniego:
z "final" kompilator może wywołać
CDerived::DoSomething()
bezpośrednio z wewnątrzBlah()
lub nawet w tekście. Bez tego musi wygenerować pośrednie wywołanie wewnątrz,Blah()
ponieważBlah()
mogłoby zostać wywołane wewnątrz klasy pochodnej, która została przesłoniętaDoSomething()
.źródło
Nie ma nic do dodania do semantycznych aspektów „końcowego”.
Chciałbym jednak dodać do komentarza Chris Greena, że „wersja ostateczna” może stać się bardzo ważną techniką optymalizacji kompilatora w nie tak odległej przyszłości. Nie tylko w prostym przypadku, o którym wspomniał, ale także w przypadku bardziej złożonych hierarchii klas w świecie rzeczywistym, które można „zamknąć” za pomocą „wersji ostatecznej”, umożliwiając kompilatorom generowanie wydajniejszego kodu rozsyłającego niż w przypadku zwykłego podejścia vtable.
Jedną z kluczowych wad vtables jest to, że dla każdego takiego wirtualnego obiektu (przy założeniu 64-bitowego na typowym procesorze Intela) sam wskaźnik zjada 25% (8 z 64 bajtów) linii pamięci podręcznej. W aplikacjach, które lubię pisać, bardzo boli. (I z mojego doświadczenia wynika, że jest to argument nr 1 przeciwko C ++ z purystycznego punktu widzenia wydajności, tj. Przez programistów C).
W aplikacjach, które wymagają ekstremalnej wydajności, co nie jest tak niezwykłe w C ++, może to rzeczywiście stać się niesamowite, nie wymagając ręcznego obejścia tego problemu w stylu C lub dziwnego żonglowania szablonami.
Ta technika jest znana jako dewirtualizacja . Termin, który warto zapamiętać. :-)
Niedawne przemówienie Andrei Alexandrescu wyjaśnia, w jaki sposób można dziś obejść takie sytuacje i jak „ostateczne” może być częścią rozwiązywania podobnych przypadków „automatycznie” w przyszłości (omówione ze słuchaczami):
http://channel9.msdn.com/Events/GoingNative/2013/Writing-Quick-Code-in-Cpp-Quickly
źródło
Finału nie można zastosować do funkcji niewirtualnych.
Możliwość oznaczenia metody niewirtualnej jako „ostatecznej” nie miałaby większego sensu. Dany
a->foo()
zawsze zadzwonięA::foo
.Ale jeśli A
virtual
:: foo byłby, to B :: foo by go zastąpił. Może to być niepożądane, dlatego też miałoby sens, aby funkcja wirtualna była ostateczna.Pytanie brzmi jednak, dlaczego pozwalają na końcowy funkcji wirtualnych. Jeśli masz głęboką hierarchię:
Następnie
final
ustawia „podłogę” na temat tego, ile można zignorować. Inne klasy mogą rozszerzać A i B i zastępować jefoo
, ale jeśli klasa rozszerza C, to nie jest to dozwolone.Więc prawdopodobnie nie ma sensu tworzyć foo „najwyższego poziomu”
final
, ale może mieć sens niżej.(Myślę jednak, że jest miejsce, aby rozszerzyć słowa końcowe i zastąpić je członkami niewirtualnymi. Miałyby jednak inne znaczenie).
źródło
final
. Na przykład, jeśli wiesz, że chcesz, aby wszyscyShape
-foo()
coś wstępnie zdefiniowanego i określonego, którego żaden kształt pochodny nie powinien modyfikować. A może się mylę i jest lepszy wzorzec do zastosowania w tym przypadku? EDYCJA: Och, może dlatego, że w takim przypadku po prostu nie powinno się po prostufoo()
virtual
zaczynać od najwyższego poziomu ? Ale mimo to, może być ukryte, nawet jeśli nazywa się poprawnie (polimorficznie) poprzezShape*
...Przykład użycia „końcowego” słowa kluczowego, które lubię, jest następujący:
źródło
final
dodaje wyraźny zamiar nie zastępowania funkcji i spowoduje błąd kompilatora, jeśli zostanie to naruszone:W obecnym stanie kod kompiluje się i
B::foo
zastępujeA::foo
.B::foo
jest również wirtualny. Jeśli jednak zmienimy # 1 navirtual int foo() final
, jest to błąd kompilatora i nie możemy go przesłonićA::foo
dalej w klasach pochodnych.Zauważ, że nie pozwala nam to na „ponowne otwarcie” nowej hierarchii, tj. Nie ma sposobu na utworzenie
B::foo
nowej, niepowiązanej funkcji, która mogłaby być niezależnie na czele nowej wirtualnej hierarchii. Gdy funkcja jest ostateczna, nie można jej już nigdy zadeklarować w żadnej klasie pochodnej.źródło
Ostatnie słowo kluczowe pozwala zadeklarować metodę wirtualną, nadpisać ją N razy, a następnie nakazać, aby „tego nie można już przesłonić”. Przydałoby się to w ograniczaniu użycia Twojej klasy pochodnej, abyś mógł powiedzieć „Wiem, że moja superklasa pozwala ci to zastąpić, ale jeśli chcesz wyprowadzić ode mnie, nie możesz!”.
Jak wskazywały inne plakaty, nie można go zastosować do funkcji niewirtualnych.
Jednym z celów końcowego słowa kluczowego jest zapobieganie przypadkowemu nadpisaniu metody. W moim przykładzie DoStuff () mogła być funkcją pomocniczą, której klasa pochodna musi po prostu zmienić nazwę, aby uzyskać prawidłowe zachowanie. Bez wersji ostatecznej błąd nie zostałby wykryty przed testami.
źródło
Ostatnie słowo kluczowe w C ++ dodane do funkcji zapobiega jej przesłonięciu przez klasę bazową. Również po dodaniu do klasy zapobiega dziedziczeniu jakiegokolwiek typu. Rozważmy następujący przykład, który pokazuje użycie końcowego specyfikatora. Ten program nie kompiluje się.
Również:
źródło
Uzupełnienie odpowiedzi Mario Knezovicia:
Powyższy kod przedstawia teorię, ale nie został faktycznie przetestowany na prawdziwych kompilatorach. Bardzo cenione, jeśli ktoś wklei zdemontowane wyjście.
źródło