Sunday, September 28, 2008

C++ Coding Standard: Inheritance

Since polymorphic behavior is one of the cornerstones of object-oriented design, our C++ coding standard has to provide some pointers on how to apply inheritance.
  • 8.1. Public inheritance must only be used to model the "is a" relation.
  • 8.2. Use private and protected inheritance sparingly, and only to model the "looks like a" relation.
  • 8.3. Only inherit publicly from an abstract base class.
  • 8.4. Destructors of public base classes must be pure virtual (but implemented).
  • 8.5. Don't use multiple inheritance.
Apart from Rule 8.1, which is an unbreachable commandment, you may from time to time find yourself wanting to break one of the other rules. Fine, but you better have a good reason for doing so.

Rule 8.2 used to read "Use only public inheritance".  The original rationale was this:  You might find private or protected inheritance useful to save a few lines of code somewhere. But I claim you should just duplicate the lines of code, rather than couple together classes which are so different conceptually that public inheritance can't be used. They might be structurally the same today, but if they are conceptually different, that structural similarity may change over time and give you a huge headache.

There's some truth to what's said above, so use private inheritance sparingly. But I softened my stance about private inheritance when I realized how useful the C++ using declaration is. You can re-use as many of the member functions of a private base class as you want with simple one-liners, without allowing polymorphic access to the base class. That way your class can expose all the useful parts of, say, std::map<A,B>, without the risk of someone confusing your class with the underlying map.

I know that the textbooks say private inheritance models "is implemented in terms of", and it's OK to think of it that way. But containment seems like a better model of "in terms of", and this "using" idiom seems to me to be the most useful part of private inheritance, so I'm going to start thinking of private inheritance modeling "looks like". It looks like std::map, for instance, but it isn't one.

Rule 8.3 springs from the same concern about coupling as 8.2. If you inherit from a non-abstract class, you're tying yourself to an interface that may change over time to meet the evolving needs of the parent class. Keeping base classes abstract means that you can change the behavior or implementation of any concrete class without shaking up some other concrete class.

It sounds odd to implement a pure virtual function, as Rule 8.4 demands, but it is in fact a legal thing to do. Legal and useful: there's little chance that someone will later remove the destructor from the class, so it will always be there to make the class abstract. When might you break this rule? If you have a memory-sensitive class where subclasses will not be used polymorphically (or do not require polymorphic destruction), you can disregard the rule and get rid of the virtual table pointer. But that's really weird: why didn't you use private inheritance if the classes are not to be used polymorphically? It sounds like you are not really modeling "is a", and you're probably skating on thin ice.

I suppose there are applications for multiple inheritance, but even if you avoid the diamond problem, you still often end up with extra complications in code that uses multiple inheritance. Before declaring a class public Fish, public Fowl, ask yourself if there is another solution, and if the derived class truly "is a" Fish and a Fowl.

Next: preprocessor issues.

No comments: