It's often useful from a design perspective to be able to mark things as unchanging. In the same way the const
provides compiler guards and indicates that a state should not change, final
can be used to indicate that behavior should not change any further down the inheritance hierarchy.
Example
Consider a video game where vehicles take the player from one location to another. All vehicles should check to make sure they are traveling to a valid location prior to departure (making sure the base at the location is not destroyed, e.g.). We can start off using the non-virtual interface idiom (NVI) to guarantee that this check is made regardless of the vehicle.
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;
};
Now let's say we have flying vehicles in our game, and something that all flying vehicles require and have in common is that they must go through a safety inspection check inside the hangar prior to take-off.
Here we can use final
to guarantee that all flying vehicles will go through such an inspection and also communicate this design requirement of flying vehicles.
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;
};
By using final
in this way, we are effectively sort of extending the flexibility of the non-virtual interface idiom to provide uniform behavior down the inheritance hierarchy (even as an afterthought, countering the fragile base class problem) to virtual functions themselves. Furthermore, we buy ourselves wiggle room to make central changes that affect all flying vehicle types as an afterthought without modifying each and every flying vehicle implementation that exists.
This is one such example of using final
. There are contexts you will encounter where it simply doesn't make sense for a virtual member function to be overridden any further -- to do so might lead to a brittle design and a violation of your design requirements.
That's where final
is useful from a design/architectural perspective.
It's also useful from an optimizer's perspective since it provides the optimizer this design information that allows it to devirtualize virtual function calls (eliminating the dynamic dispatch overhead, and often more significantly, eliminating an optimization barrier between caller and callee).
Question
From the comments:
Why would final and virtual ever be used at the same time?
It doesn't make sense for a base class at the root of a hierarchy to declare a function as both virtual
and final
. That seems quite silly to me, as it would make both compiler and human reader have to jump through unnecessary hoops which can be avoided by simply avoiding virtual
outright in such a case. However, subclasses inherit virtual member functions like so:
struct Foo
{
virtual ~Foo() {}
virtual void f() = 0;
};
struct Bar: Foo
{
/*implicitly virtual*/ void f() final {...}
};
In this case, whether or not Bar::f
explicitly uses the virtual keyword, Bar::f
is a virtual function. The virtual
keyword then becomes optional in this case. So it might make sense for Bar::f
to be specified as final
, even though it is a virtual function (final
can only be used for virtual functions).
And some people may prefer, stylistically, to explicitly indicate that Bar::f
is virtual, like so:
struct Bar: Foo
{
virtual void f() final {...}
};
To me it's kind of redundant to use both virtual
and final
specifiers for the same function in this context (likewise virtual
and override
), but it's a matter of style in this case. Some people might find that virtual
communicates something valuable here, much like using extern
for function declarations with external linkage (even though it's optional lacking other linkage qualifiers).