Geralmente, do ponto de vista do design, é útil poder marcar as coisas como imutáveis. Da mesma forma, o const
compilador fornece proteções e indica que um estado não deve ser alterado. final
Pode ser usado para indicar que o comportamento não deve ser alterado ainda mais na hierarquia de herança.
Exemplo
Considere um videogame no qual os veículos levam o jogador de um local para outro. Todos os veículos devem verificar se estão viajando para um local válido antes da partida (certificando-se de que a base no local não seja destruída, por exemplo). Podemos começar usando o idioma da interface não virtual (NVI) para garantir que essa verificação seja feita independentemente do veí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;
};
Agora, digamos que temos veículos voadores em nosso jogo, e algo que todos os veículos voadores exigem e têm em comum é que eles devem passar por uma inspeção de segurança dentro do hangar antes da decolagem.
Aqui, podemos final
garantir que todos os veículos voadores passarão por essa inspeção e também comunicar esse requisito de design dos veículos voadores.
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;
};
Ao usar final
dessa maneira, estamos efetivamente estendendo a flexibilidade do idioma da interface não virtual para fornecer um comportamento uniforme na hierarquia de herança (mesmo como uma reflexão tardia, combatendo o frágil problema da classe base) para as próprias funções virtuais. Além disso, adquirimos espaço de manobra para fazer alterações centrais que afetam todos os tipos de veículos voadores como uma reflexão tardia, sem modificar cada implementação de veículo voador existente.
Este é um exemplo de uso final
. Existem contextos que você encontrará onde simplesmente não faz sentido a substituição de uma função de membro virtual - isso pode levar a um design frágil e a uma violação dos seus requisitos de design.
É aí que final
é útil do ponto de vista de design / arquitetura.
Também é útil do ponto de vista de um otimizador, pois fornece ao otimizador essas informações de design que permitem desirtualizar as chamadas de função virtual (eliminando a sobrecarga dinâmica de despacho e, muitas vezes de maneira mais significativa, eliminando uma barreira de otimização entre o chamador e o chamado).
Questão
Dos comentários:
Por que final e virtual seriam usados ao mesmo tempo?
Não faz sentido para uma classe base na raiz de uma hierarquia declarar uma função como ambos virtual
e final
. Isso me parece bastante tolo, pois faria com que o compilador e o leitor humano tivessem que passar por obstáculos desnecessários que podem ser evitados simplesmente evitando-se de virtual
imediato nesse caso. No entanto, as subclasses herdam funções de membro virtual da seguinte forma:
struct Foo
{
virtual ~Foo() {}
virtual void f() = 0;
};
struct Bar: Foo
{
/*implicitly virtual*/ void f() final {...}
};
Nesse caso, se Bar::f
usa ou não explicitamente a palavra-chave virtual, Bar::f
é uma função virtual. A virtual
palavra-chave se torna opcional neste caso. Portanto, pode fazer sentido Bar::f
ser especificado como final
, mesmo que seja uma função virtual ( sófinal
pode ser usada para funções virtuais).
E algumas pessoas podem preferir, estilisticamente, indicar explicitamente que Bar::f
é virtual, assim:
struct Bar: Foo
{
virtual void f() final {...}
};
Para mim, é meio redundante usar ambos virtual
e final
especificadores para a mesma função neste contexto (da mesma forma virtual
e override
), mas é uma questão de estilo nesse caso. Algumas pessoas podem achar que virtual
comunica algo valioso aqui, como usar extern
para declarações de função com ligação externa (mesmo que seja opcional sem outros qualificadores de ligação).