The inexorable march of progress continues in the Mozilla Framework Based on Templates with the addition of MOZ_FINAL
, through which you can limit various forms of inheritance in C++.
Traditional C++ inheritance mostly can’t be controlled
In C++98 any class can be inherited. (An extremely obscure pattern will prevent this, but it has down sides.) Sometimes this makes sense: it’s natural to subclass an abstract List
class as LinkedList
, or LinkedList
as CircularLinkedList
. But sometimes this doesn’t make sense. StringBuilder
certainly could inherit from Vector<char>
, but doing so might expose many Vector
methods that don’t belong in the StringBuilder
concept. It would be more sensible for StringBuilder
to contain a private Vector<char>
which StringBuilder
member methods manipulated. Preventing Vector
from being used as a base class would be one way (not necessarily the best one) to avoid this conceptual error. But C++98 doesn’t let you easily do that.
Even when inheritance is desired, sometimes you don’t want completely-virtual methods. Sometimes you’d like a class to implement a virtual method (virtual perhaps because its base declared it so) which derived classes can’t override. Perhaps you want to rely on that method being implemented only by your base class, or perhaps you want it “fixed” as an optimization. Again in C++98, you can’t do this: public virtual functions are overridable.
C++11’s contextual final
keyword
C++11 introduces a contextual final
keyword for these purposes. To prevent a class from being inheritable, add final
to its definition just after the class name (the class can’t be unnamed).
struct Base1 final { virtual void foo(); }; // ERROR: can't inherit from B. struct Derived1 : public Base1 { }; struct Base2 { }; // Derived classes can be final too. struct Derived2 final : public Base2 { };
Similarly, a virtual member function can be marked as not overridable by placing the contextual final
keyword at the end of its declaration, before a terminating semicolon, body, or = 0
.
struct Base { virtual void foo() final; }; struct Derived : public Base { // ERROR: Base::foo was final. virtual void foo() { } };
Introducing MOZ_FINAL
mfbt now includes support for marking classes and virtual member functions as final using the MOZ_FINAL
macro in mozilla/Attributes.h
. Simply place it in the same position as final
would occur in the C++11 syntax:
#include "mozilla/Attributes.h" class Base { public: virtual void foo(); }; class Derived final : public Base { public: /* * MOZ_FINAL and MOZ_OVERRIDE are composable; as a matter of * style, they should appear in the order MOZ_FINAL MOZ_OVERRIDE, * not the other way around. */ virtual void foo() MOZ_FINAL MOZ_OVERRIDE { } virtual void bar() MOZ_FINAL; virtual void baz() MOZ_FINAL = 0; };
MOZ_FINAL
expands to the C++11 syntax or its compiler-specific equivalent whenever possible, turning violations of final
semantics into compile-time errors. The same compilers that usefully expand MOZ_OVERRIDE
also usefully expand MOZ_FINAL
, so misuse will be quickly noted.
One interesting use for MOZ_FINAL
is to tell the compiler that one worrisome C++ trick sometimes isn’t. This is the virtual-methods-without-virtual-destructor trick. It’s used when a class must have virtual functions, but code doesn’t want to pay the price of destruction having virtual-call overhead.
class Base { public: virtual void method() { } ~Base() { } }; void destroy(Base* b) { delete b; // may cause a warning }
Some compilers warn when they instantiate a class with virtual methods but without a virtual destructor. Other compilers only emit this warning when a pointer to a class instance is delete
d. The reason is that in C++, behavior is undefined if the static type of the instance being deleted isn’t the same as its runtime type and its destructor isn’t virtual. In other words, if ~Base()
is non-virtual, destroy(new Base)
is perfectly fine, but destroy(new DerivedFromBase)
is not. The warning makes sense if destruction might miss a base class — but if the class is marked final
, it never will! Clang silences its warning if the class is final
, and I hope that MSVC will shortly do the same.
What about NS_FINAL_CLASS
?
As with MOZ_OVERRIDE
we had a gunky XPCOM static-analysis NS_FINAL_CLASS
macro for final classes. (We had no equivalent for final methods.) NS_FINAL_CLASS
too was misplaced as far as C++11 syntax was concerned, and it too has been deprecated. Almost all uses of NS_FINAL_CLASS
have now been removed (the one remaining use I’ve left for the moment due to an apparent Clang bug I haven’t tracked down yet), and it shouldn’t be used.
(Side note: In replacing NS_FINAL_CLASS
with MOZ_FINAL
, I discovered that some of the existing annotations have been buggy for months! Clearly no one’s done static analysis builds in awhile. The moral of the story: compiler static analyses that happen for every single build are vastly superior to user static analyses that happen only in special builds.)
Summary
If you don’t want a class to be inheritable, add MOZ_FINAL
to its definition after the class name. If you don’t want a virtual member function to be overridden in derived classes, add MOZ_FINAL
at the end of its declaration. Some compilers will then enforce your wishes, and you can rely on these requirements rather than hope for the best.