17

I know that final keyword is used to prevent virtual method from being overriden by derived classes. However, I can't find any useful example when I should really use final keyword with virtual method. Even more, it feels like usage of final with virtual methods is a bad smell since it disallows programmers to extend class in future.

My question is next:

Is there any useful case when I really should use final in virtual method declaration?

dmytroy
  • 283

3 Answers3

19

An example of why you would need such a thing:

Suppose class A defines a virtual function prepareEnvelope().

And suppose that class B extends A and overrides prepareEnvelope(), implementing it as a sequence of calls to its own virtual methods stuffEnvelope(), lickEnvelope() and sealEnvelope().

Obviously, since these new methods are virtual, class B intends to allow derived classes to override them to provide their own implementations; however class B probably does not want to allow a derived class to also override prepareEnvelope() and thus potentially change the order of stuff, lick, seal, or omit invoking one of them.

So, in this case class B would declare prepareEnvelope() as final.

The example above is a case of the template method pattern (https://en.wikipedia.org/wiki/Template_method_pattern) where class B implements prepareEnvelope() as a template method to achieve a certain goal, and presumably also contains other functionality (e.g. fields) to support that goal. Any derived class should conform to that template, otherwise the supported functionality is provided in vain, and in certain cases failure to use that functionality may cause malfunction. (If the lickEnvelope() method is not invoked, then no saliva will be consumed, so who knows, the saliva container might overflow.) So, if you want to build a class which deviates from the pattern established by B, then you should not be deriving from B, you should be deriving from the basemost class A.

Another example:

The same thing can be seen in a more simple scenario which most programmers encounter at some point: Let's say the basemost class A has a cleanUp() method, and derived class B overrides cleanUp() to perform some very important actions which absolutely must be performed or else the Universe will self-destruct. In this case, class B may not want to leave it up to derived classes to decide whether their overrides of cleanUp() will invoke B::cleanUp() or not. To this effect, class B may make cleanUp() final, and declare an empty protected virtual method onCleanUp() which is invoked from within cleanUp() to allow descendants to override it in order to do their own cleanup. This means that when cleanUp() is invoked on the object, method B::cleanUp() will always be invoked, and thus the Universe will be saved, while overrides of onCleanUp() may forget invoking B::onCleanUp(), and nothing bad will happen, because it is empty.

Generally:

In the general case, a class declares a method as final if the class contains functionality which requires that the method must be invoked and must work exactly as defined. In these cases, derived classes must not be allowed to override the method, because the overrides may work differently, or they may omit invoking the base method.

Mike Nakis
  • 32,803
6

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).

0
  1. It enables lot of optimisations, because it may be known at compile time which function is called.

  2. Be careful throwing the word "code smell" around. "final" doesn't make it impossible to extend the class. Double click on the "final" word, hit backspace, and extend the class. HOWEVER final is an excellent documentation that the developer doesn't expect you to override the function, and that because of that the next developer should be very careful, because the class might stop working properly if the final method gets overridden that way.

gnasher729
  • 49,096