Friday, January 25, 2013

C++ Private Inheritance

For a long time I believed private inheritance was such an arcane feature that it should be avoided. In my C++ Coding Standard, 8.2 stated "Use only public inheritance." Here was my rationale:

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.

I think that rationale is pretty good, but now I've decided it might be worthwhile to use private inheritance in some cases. For instance, it might make sense to inherit from some STL container class, and promote some of its lookup functions or subtypes to "public" with "using" declarations. Like this:

  class Foo : private std::map<int, Bar *> {

  public:
    typedef std::map<int, Bar *> Base;
    using Base::key_type;
    using Base::data_type;
    using Base::value_type;
    using Base::const_iterator;
    using Base::begin;
    using Base::end;
    using Base::empty;
    using Base::size;
    using Base::find;

The likelihood of the structural change mentioned above is very low in this case, and the "using" declarations -- which I'll admit I didn't know could be used that way when I first wrote the standard -- allow you to reuse worthwhile parts of the base class in a very clean and foolproof manner. Later if you realize there's another bit of functionality you want to expose publicly, you only have to write a one-liner, not a wrapper function. Meanwhile, things you might not want exposed -- like operator[]() -- stay out of the interface.

But the clincher for me is that you had better not inherit publicly from a class like std::map.  Why?  Because it doesn't have a virtual destructor.  What if someone -- for whatever reason -- ends up with a pointer to a std::map which is actually one of your Foo objects, and wants to delete it?  Only the map destructor will be called.  If ~Foo() was supposed to delete its values, that won't happen.  Similarly, public inheritance means that someone could take a pointer to a Foo, and then use map's copy constructor or assignment operator to make a simple pointer copy of the Foo, perhaps unintentionally.  Then if the original was deleted, and deleted its values, the copy would hold stale pointers.

Interestingly, I didn't seem very worried about that situation when I annotated the standard.  I wrote that you might sometimes break 8.4 -- "Destructors of base classes must be pure virtual (but implemented)." -- giving this bad advice:

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.

That seems ridiculous now.  That is actually exactly the use case for private inheritance:  you want to reuse some code, but not polymorphically.

This all came to the fore today when I considered making a class 'public std::pair', so it could model an Edge in a Boost Graph Library edge_list<>, which requires 'first' and 'second' members (at least in the iterator class you instantiate edge_list<> with).  But recently a friend of mine had twitted me on another class I wrote that inherits publicly from an STL container, so I started to think about these issues.

I'm still not sure how I'll implement my Edge class -- except that it won't be public pair -- but I see that I need to change the coding standard someday soon, to reflect the reasonable usage of private inheritance.


Stumble Upon Toolbar

No comments: