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.
AIUI, static analysis builds haven’t been run for over a year.
Is there any chance you can add a
MOZ_MUST_OVERRIDE
macro to replace the existingNS_MUST_OVERRIDE
? It’s a useful macro, I had reason to use it just the other day, but it too only runs on static analysis builds :/ Maybe C++11 doesn’t have anything that will allow it.Comment by njn — 26.11.11 @ 12:41
C++11 doesn’t have anything like that, to the best of my knowledge.
I remember seeing
NS_MUST_OVERRIDE
once before and wondering why it existed. I’m still not clear on what it does that making the base method pure-virtual can’t do. Eliminate the need for another subclass just for what would have been the base-class definition of the method? Seems like a dodgy reason to me. It doesn’t help that the only use ofNS_MUST_OVERRIDE
in the codebase is onnsFrame
, in a case which does suggest that must-override semantics were needed only to avoid having an extra subclass of it. But maybe I’m missing something.Comment by Jeff — 26.11.11 @ 13:45
You can’t make the base method pure-virtual if you need to instantiate it.
The specific case I encountered was that class gfxTextRun has a virtual ComputeSize() method, and its subclass nsTransformedTextRun overrides that. You can’t make gfxTextRun::ComputeSize() pure virtual because you have to instantiate gfxTextRun objects, and nsTransformedTextRun must have its own definition of ComputeSize() because it has extra members and so is bigger than gfxTextRun.
Comment by njn — 27.11.11 @ 00:42
One advantage of inheritance over composition is that you can change the visibility of inherited methods, whereas with composition you have to write your own forwarding methods. For instance, nsCOMArray has a private member nsVoidArray mArray:
but if it instead had privately inherited nsVoidArray then this would have sufficed:
Comment by Neil Rashbrook — 28.11.11 @ 04:35
One of the compilers we use always warns when a class has a virtual method without a virtual destructor. Since we compile with
-Werror
, we code around it and take the minor performance hit of making the destructor virtual when it doesn’t always need to be (because derived class objects are not deleted through base class pointers).If and when that compiler supports C++11’s
final
, we could go back and fix those (if we remember where they are; frankly, I wouldn’t bet on that), but I’m not holding my breath yet.Comment by Adam Rosenfield — 28.11.11 @ 20:38
Would you happen to know which syntax is used between “final” and “__final”?
Comment by Alex — 22.05.12 @ 07:01
For gcc, I take it? We use
final
when we’re compiling C++11 code (in other words, it’s being compiled with-std={gnu,c}++{0x,11}
) with a new enough gcc, and we use__final
when we’re compiling non-C++11 code with a new enough gcc. Seemfbt/Attributes.h
for the exact logic. Messy, sure, but write-once messy.Comment by Jeff — 22.05.12 @ 10:50