You (probably) do want final classes?

In the previous post I’ve discussed the C++11 final keyword and how it can be used. I also gave a guideline that you shouldn’t use final on non-polymorphic classes. My reasoning was as follows:

  1. For some classes - like policy classes or any other class where you might want to have the EBO - making them final can be harmful.

  2. For other classes - those which aren’t used polymorphically - final is unnecessary. Every (good) C++ developer is taught early on that you shouldn’t use a class in a polymorphic inheritance hierarchy if it doesn’t have any virtual functions. Public inheritance there makes no sense and is harmful. Everybody knows that, the final is just there to enforce it.

  3. There are only little use cases for final in polymorphic hierarchies. So in general you don’t need it.

This has spawned a discussion both on reddit and in the blog comments, so I decided to write this follow-up to unify the discussion and write about each argument.

For polymorphic classes: Final can be used to enforce MEC++ Item 33

Jon Kalb has reminded me in the previous post that final can be used to enforce Item 33 of Modern Effective C++. It states that “You should make non-leaf classes abstract”. Scott argues that you shouldn’t inherit from non-abstract classes, a guideline I agree partly with. If you decide to do so, you get a couple of problems and “bad” abstractions.

If you want to enforce that, you need a way to prevent inheritance from the leaf classes which aren’t abstract. This is exactly what final does. Just mark every concrete class final and you cannot violate that guideline.

For polymorphic classes: Final can be used as an optimization

A couple of people mentioned that final can help the compiler optimize, which is true.

There is a common compiler optimization called devirtualization. Calling a virtual function is more expensive then just calling a regular function: the vptr must be followed to get the right function pointer which must be dereferenced and call. Those are indirections and indirections are expensive due to caches and other factors.

Furthermore: they cannot be inlined because of that. This is huge.

So if the compiler can get static information about which function needs to be called, it can just insert a regular function call. But when does the compiler know that? After all, virtual functions are there because you do not know which function to call.

It can happen when you have a function that doesn’t take the base class but a class that is lower in the hierarchy.

Is it lower or higher? I mean a class that is more derived than the base class.

In particular: If the class is a leaf. Consider the following translation unit:

struct base
    virtual void foo() = 0;

struct derived : base
    void foo() override;

void func(const derived &d)

Here the compiler has more information than if func would just take base. In particular, if it can see that there is know type further derived than derived, it can devirtualize the call to foo() because it knows that the type must be derived or children and that derived has no children.

In bigger projects with multiple translation units this is more difficult to see than here. For example, there could be another file that with a class more_derived. But sometimes link-time optimization can show that anyway. Yet, putting final on derived let’s the compiler easily see that and thus encourage the optimization.

Okay, I’ve already said in the previous post that I just couldn’t come up with reasons for polymorphic classes. I knew about devirtualization but haven’t thought about it when writing. This is because I rarely use those classical OOP inheritance hierarchies.

Yes, standardese is centered around one. But it isn’t like my other projects.

So on to the more interesting discussion points: non-polymorphic classes and final.

final as wide vs narrow contract

/u/quicknir made an interesting comment on reddit that got more upvotes than my blog post itself, so many people seem to agree.

He wrote about wide vs narrow contracts. If a class is final, this is a narrow contract; you cannot inherit. If you one day decide to remove the final, it will widen the contract. This isn’t a breaking change. The other way round, however, is. So when in doubt, always use final on non-polymorphic classes; he argues.

He also wrote that my logic is based on the fact that non-final is the default and asked me to consider a situation where final is default and there is a nonfinal keyword. Would I really argue for putting nonfinal everywhere?

This got me thinking. I’m not someone who blindly keeps a position no matter the other arguments. If someone gives a good technical argument, I can sometimes switch sides.

And (unfortunately) this is a good technical argument.

So let’s consider a world where final is default. This world enforces the guideline that you shouldn’t inherit from non-polymorphic classes automatically. As a class author you have to do active work in order to allow inheritance. Arguably, this is a better world.

And in this world I would have learned C++ being taught that classes are final by default. And I would see the benefits of that approach. Maybe in this world my main argument against final - EBO - would be non-existent, because it simply isn’t considered.

Of course I wouldn’t argue for nonfinal everywhere. Nobody would.

So yes, /u/quicknir is right, my argument comes from inertia.

So you should put final on every non-polymorphic class?

So know I go and add final onto every non-polymorphic class in my codebases, right?

I probably won’t.

I’m a library author, I don’t know how my classes are used. Maybe someone has a good reason for (non-public) inheritance. While policy classes are a really tiny minority of classes, for me this isn’t true.

memory is centered around policy classes. Almost every class can be templated with an allocator. And those allocators can be empty, so inheritance is used to benefit from EBO.

And sadly the situation with EBO isn’t resolved. While I agree that using EBO is a hack - like most C++ techniques to be honest, it is still an essential part of my design. As such, I prohibit final on allocators and the other policy classes I use and I still encourage you to do so for policy classes as well.

But I want a better solution that doesn’t conflict with final. There are some alternatives:

  1. Simply allow private inheritance from final classes. This allows EBO usage but one can also argue that it breaks the use of final.

  2. Create a new attribute, like [[maybe_empty]]. If you put that on a class members, the compiler is allowed to give them zero size. But one could argue that this is “too big” for an attribute.

  3. Give alignas(0) a new meaning, currently it is ignored. Its meaning could be changed to give a class as member the size of 0.

  4. Create one of those “magic” standard library classes. A std::compressed_pair, similar to Boost’s but that can work on final types. But I personally really hate standard library components a “normal” user cannot implement.

Maybe someone with experience in the standardization process can draft a proposal regarding EBO. Given a proper solution to the situation of empty classes, my view on final is going to change and I will use it everywhere.

But as the current status is, I won’t go and add final to my classes. And if you do, please keep the EBO consequences in mind and don’t do it for policy classes. It makes generic code easier.

If you've liked this blog post, consider donating or otherwise supporting me.

This blog post was written for my old blog design and ported over. If there are any issues, please let me know.