26.11.11

Introducing MOZ_FINAL: prevent inheriting from a class, or prevent overriding a virtual function

Tags: , , , , , , , — Jeff @ 09:17

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

7 Comments »

  1. 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 existing NS_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

  2. 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 of NS_MUST_OVERRIDE in the codebase is on nsFrame, 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

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

  4. 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:

    public:
        PRInt32 Count() const {
            return mArray.Count();
         }
    

    but if it instead had privately inherited nsVoidArray then this would have sufficed:

    public:
        using nsVoidArray::Count;
    

    Comment by Neil Rashbrook — 28.11.11 @ 04:35

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

  6. Would you happen to know which syntax is used between “final” and “__final”?

    Comment by Alex — 22.05.12 @ 07:01

  7. 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. See mfbt/Attributes.h for the exact logic. Messy, sure, but write-once messy.

    Comment by Jeff — 22.05.12 @ 10:50

RSS feed for comments on this post. TrackBack URI

Leave a comment

HTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>