A menudo es útil desde una perspectiva de diseño poder marcar cosas como inmutables. Del mismo modo, const
proporciona protectores del compilador e indica que un estado no debe cambiar, final
puede usarse para indicar que el comportamiento no debería cambiar más abajo en la jerarquía de herencia.
Ejemplo
Considere un videojuego donde los vehículos llevan al jugador de un lugar a otro. Todos los vehículos deben verificar para asegurarse de que están viajando a un lugar válido antes de la salida (asegurándose de que la base en el lugar no esté destruida, por ejemplo). Podemos comenzar usando el lenguaje de interfaz no virtual (NVI) para garantizar que esta verificación se realice independientemente del vehículo.
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;
};
Ahora digamos que tenemos vehículos voladores en nuestro juego, y algo que todos los vehículos voladores requieren y tienen en común es que deben pasar por una inspección de seguridad dentro del hangar antes del despegue.
Aquí podemos usar final
para garantizar que todos los vehículos voladores pasarán por dicha inspección y también comunicar este requisito de diseño de los vehículos voladores.
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;
};
Al usarlo final
de esta manera, estamos efectivamente ampliando la flexibilidad del lenguaje de interfaz no virtual para proporcionar un comportamiento uniforme en la jerarquía de herencia (incluso como una ocurrencia tardía, contrarrestando el frágil problema de la clase base) a las funciones virtuales. Además, nos compramos espacio de maniobra para realizar cambios centrales que afectan a todos los tipos de vehículos voladores como una ocurrencia tardía sin modificar cada una de las implementaciones de vehículos voladores que existen.
Este es un ejemplo de uso final
. Hay contextos que encontrará en los que simplemente no tiene sentido que una función miembro virtual se anule aún más; hacerlo podría conducir a un diseño frágil y una violación de sus requisitos de diseño.
Ahí es donde final
es útil desde una perspectiva de diseño / arquitectura.
También es útil desde la perspectiva de un optimizador, ya que proporciona al optimizador esta información de diseño que le permite desvirtualizar las llamadas a funciones virtuales (eliminando la sobrecarga de despacho dinámico y, a menudo, de manera más significativa, eliminando una barrera de optimización entre la persona que llama y la persona que llama).
Pregunta
De los comentarios:
¿Por qué alguna vez se usaría final y virtual al mismo tiempo?
No tiene sentido que una clase base en la raíz de una jerarquía declare una función como ambos virtual
y final
. Eso me parece bastante tonto, ya que haría que tanto el compilador como el lector humano tengan que saltar a través de aros innecesarios que se pueden evitar simplemente evitando virtual
directamente en tal caso. Sin embargo, las subclases heredan funciones de miembros virtuales de la siguiente manera:
struct Foo
{
virtual ~Foo() {}
virtual void f() = 0;
};
struct Bar: Foo
{
/*implicitly virtual*/ void f() final {...}
};
En este caso, Bar::f
usar explícitamente o no la palabra clave virtual, Bar::f
es una función virtual. La virtual
palabra clave se convierte en opcional en este caso. Por lo que podría tener sentido para Bar::f
ser especificado como final
, a pesar de que es una función virtual ( final
puede solamente ser utilizado para funciones virtuales).
Y algunas personas pueden preferir, estilísticamente, indicar explícitamente que Bar::f
es virtual, así:
struct Bar: Foo
{
virtual void f() final {...}
};
Para mí es algo redundante usar ambos virtual
y los final
especificadores para la misma función en este contexto (del mismo modo virtual
y override
), pero es una cuestión de estilo en este caso. Algunas personas pueden encontrar que virtual
aquí se comunica algo valioso, al igual que el uso extern
para declaraciones de funciones con enlace externo (a pesar de que es opcional sin otros calificadores de enlace).