Il est souvent utile du point de vue de la conception de pouvoir marquer les choses comme immuables. De la même manière, le const
compilateur fournit des protections et indique qu'un état ne doit pas changer, final
peut être utilisé pour indiquer que le comportement ne doit pas changer plus bas dans la hiérarchie d'héritage.
Exemple
Imaginez un jeu vidéo où des véhicules conduisent le joueur d'un endroit à un autre. Tous les véhicules doivent vérifier pour s'assurer qu'ils se rendent à un endroit valide avant le départ (en s'assurant que la base à l'emplacement n'est pas détruite, par exemple). Nous pouvons commencer par utiliser l'idiome d'interface non virtuelle (NVI) pour garantir que cette vérification est effectuée quel que soit le véhicule.
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;
};
Maintenant, disons que nous avons des véhicules volants dans notre jeu, et quelque chose que tous les véhicules volants ont besoin et ont en commun, c'est qu'ils doivent passer un contrôle d'inspection de sécurité à l'intérieur du hangar avant le décollage.
Ici, nous pouvons utiliser final
pour garantir que tous les véhicules volants subiront une telle inspection et communiquer également cette exigence de conception des véhicules volants.
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;
};
En utilisant final
de cette manière, nous étendons effectivement la flexibilité de l'idiome d'interface non virtuelle pour fournir un comportement uniforme dans la hiérarchie d'héritage (même après coup, pour contrer le problème de la classe de base fragile) aux fonctions virtuelles elles-mêmes. De plus, nous nous achetons une marge de manœuvre pour effectuer des changements centraux qui affectent tous les types de véhicules volants après coup sans modifier chaque implémentation de véhicule volant qui existe.
Ceci est un exemple d'utilisation final
. Il y a des contextes que vous rencontrerez où il est tout simplement insensé qu'une fonction de membre virtuel soit outrepassée davantage - cela pourrait entraîner une conception fragile et une violation de vos exigences de conception.
C'est là qu'il final
est utile d'un point de vue design / architectural.
Il est également utile du point de vue d'un optimiseur car il fournit à l'optimiseur ces informations de conception qui lui permettent de dévirtualiser les appels de fonction virtuels (en éliminant la surcharge de répartition dynamique, et souvent de manière plus significative, en éliminant une barrière d'optimisation entre l'appelant et l'appelé).
Question
D'après les commentaires:
Pourquoi le final et le virtuel seraient-ils jamais utilisés en même temps?
Il n'est pas logique pour une classe de base à la racine d'une hiérarchie de déclarer une fonction à la fois virtual
et final
. Cela me semble assez idiot, car cela obligerait à la fois le compilateur et le lecteur humain à sauter à travers des cerceaux inutiles qui peuvent être évités en évitant simplement virtual
carrément dans un tel cas. Cependant, les sous-classes héritent des fonctions de membre virtuel comme ceci:
struct Foo
{
virtual ~Foo() {}
virtual void f() = 0;
};
struct Bar: Foo
{
/*implicitly virtual*/ void f() final {...}
};
Dans ce cas, l' Bar::f
utilisation explicite ou non du mot-clé virtuel Bar::f
est une fonction virtuelle. Le virtual
mot-clé devient alors facultatif dans ce cas. Il peut donc être judicieux de Bar::f
spécifier as final
, même s'il s'agit d'une fonction virtuelle ( final
ne peut être utilisée que pour des fonctions virtuelles).
Et certaines personnes peuvent préférer, stylistiquement, indiquer explicitement que Bar::f
c'est virtuel, comme ceci:
struct Bar: Foo
{
virtual void f() final {...}
};
Pour moi, c'est un peu redondant d'utiliser les deux virtual
et les final
spécificateurs pour la même fonction dans ce contexte (de même virtual
et override
), mais c'est une question de style dans ce cas. Certaines personnes pourraient trouver que virtual
communique quelque chose de précieux ici, un peu comme utiliser extern
pour les déclarations de fonction avec liaison externe (même si c'est facultatif sans autres qualificatifs de liaison).