Często jest użyteczne z punktu widzenia projektowania, aby móc oznaczyć rzeczy jako niezmienne. W ten sam sposób constzapewnia kompilator chroni i wskazuje, że stan nie powinien się zmienić, finalmoż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.
class Vehicle
{
public:
virtual ~Vehicle {}
bool transport(const Location& location)
{
// Mandatory check performed for all vehicle types. We could potentially
// throw or assert here instead of returning true/false depending on the
// exceptional level of the behavior (whether it is a truly exceptional
// control flow resulting from external input errors or whether it's
// simply a bug for the assert approach).
if (valid_location(location))
return travel_to(location);
// If the location is not valid, no vehicle type can go there.
return false;
}
private:
// Overridden by vehicle types. Note that private access here
// does not prevent derived, nonfriends from being able to override
// this function.
virtual bool travel_to(const Location& location) = 0;
};
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 finalzagwarantować, że wszystkie latające pojazdy przejdą taką kontrolę, a także poinformować o wymaganiach projektowych dotyczących latających pojazdów.
class FlyingVehicle: public Vehicle
{
private:
bool travel_to(const Location& location) final
{
// Mandatory check performed for all flying vehicle types.
if (safety_inspection())
return fly_to(location);
// If the safety inspection fails for a flying vehicle,
// it will not be allowed to fly to the location.
return false;
}
// Overridden by flying vehicle types.
virtual void safety_inspection() const = 0;
virtual void fly_to(const Location& location) = 0;
};
Używając finalw 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 finalto 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:
Dlaczego w tym samym czasie miałaby być używana wersja ostateczna i wirtualna?
Nie ma sensu, aby klasa podstawowa w katalogu głównym hierarchii deklarowała funkcję zarówno jako, jak virtuali final. 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ąc virtualw takim przypadku wprost. Jednak podklasy dziedziczą takie funkcje elementów wirtualnych:
struct Foo
{
virtual ~Foo() {}
virtual void f() = 0;
};
struct Bar: Foo
{
/*implicitly virtual*/ void f() final {...}
};
W tym przypadku, niezależnie od tego, czy Bar::fjawnie używa się wirtualnego słowa kluczowego, Bar::fjest funkcją wirtualną. Słowo virtualkluczowe staje się wówczas opcjonalne. Dlatego może być sensowne Bar::fokreślenie jako final, nawet jeśli jest to funkcja wirtualna ( finalmoże być używana tylko w przypadku funkcji wirtualnych).
I niektórzy ludzie wolą, stylistycznie, jawnie wskazywać, że Bar::fjest wirtualny, tak jak:
struct Bar: Foo
{
virtual void f() final {...}
};
Dla mnie to trochę zbędne stosowanie obu virtuali finalspecyfikatorów dla tej samej funkcji w tym kontekście (podobnie virtuali override), ale w tym przypadku jest to kwestia stylu. Niektóre osoby mogą uznać, że virtualprzekazuje tutaj coś cennego, podobnie jak w externprzypadku deklaracji funkcji z zewnętrznym powiązaniem (chociaż opcjonalnie nie ma innych kwalifikatorów powiązania).