Example of a useful use of private inheritance in C++

Background

C++ supports three types of inheritance: Public, private and protected.

Public inheritance is the regular inheritance in object-oriented programming: The public interface in the base class also becomes part of the public interface of the derived class. Likewise the protected section of the base class becomes part of the protected section of the derived class.

In private inheritance everything in the base class becomes part of the private interface of the derived class (and hence is not accessible from the outside when dealing with objects of the derived type).

From these only the public inheritance is a "true" inheritance from an object-oriented point of view. If you inherit privately, there is no "is-a" relationship between the derived and the base classes, and thus private inheritance does not conform to object-oriented design.

So why have this odd private inheritance at all? How can it be useful? Wouldn't composition be a better design if one wants "private inheritance"? Let me present here an example where I have found it useful.

Introducing an example case: A smart pointer

Suppose you want to create an intrusive smart pointer which supports specifying which memory allocator was used to allocate the object being managed (so that the smart pointer will be able to destroy the object using that same memory allocator). A naive implementation could look something like this:

template<typename Obj_t,
         typename Allocator = std::allocator<Obj_t> >
class IntrusivePtr
{
    Obj_t* obj;
    Allocator allocator;

 public:
    // All the necessary public methods here
};

The class has to store the allocator object (which it has to take eg. as a constructor parameter) because it is possible for allocators to have an internal state, and if this is so, it must be stored so that the allocated object can be destroyed appropriately. You cannot simply assume you can instantiate the allocator whenever it's needed (this could work with stateless allocators but not with ones with an internal state).

There's an annoying problem with this, though. Most allocators (including std::allocator) are empty. They do not have any internal state (ie. they do not have any member variables). If the allocator is empty, the IntrusivePtr above is reserving completely unused space for it by keeping an instance as member. The 'allocator' member will take at least 1 byte (which will then be expanded to the natural word size of the system for alignment reasons). Thus the size of the IntrusivePtr class is increased for no good reason: The space is not used for anything and thus it's a complete waste.

Usually one would want smart pointers (especially intrusive ones) to be as small as possible. The 'allocator' member effectively doubles the size of the class, completely uselessly if the allocator is empty. However, if we want this class to work properly with all possible allocators, we have to reserve some space for it. However, wouldn't it be nice if there was a way to avoid needlessly reserving space for empty allocators?

Empty base class optimization

Most C++ compilers implement the so-called empty base class optimization (allowed by the C++ standard). This means that if we inherit from an empty base class, the base class will take no space in the derived class at all. By "abusing" this feature we can make an improved version of the above class:

template<typename Obj_t,
         typename Allocator = std::allocator<Obj_t> >
class IntrusivePtr: public Allocator
{
    Obj_t* obj;

 public:
    // All the necessary public methods here
};

Now it's perfect spacewise: If the specified allocator does have an internal state, it will be stored in the IntrusivePtr object. However, most importantly, if the allocator is empty, it will take no space at all (at least with most compilers). The allocator can still be used for everything that the member could be used by IntrusivePtr.

However, there's a design problem here. By inheriting from Allocator we are effectively saying that IntrusivePtr is an Allocator. However, that's not what we meant. We are simply "abusing" inheritance for a compiler optimization technique, rather than what inheritance is usually used for in object-oriented programming. This is, basically, a wrong usage of inheritance. A smart pointer is not an allocator, and shouldn't be made one. (In this case the smart pointer contains an allocator, the smart pointer is not an allocator itself.)

Ok, it's mostly only a cosmetic problem, but it still bothers an object-oriented purist (like me). Isn't there anything that can be done about it?

Step in private inheritance

template<typename Obj_t,
         typename Allocator = std::allocator<Obj_t> >
class IntrusivePtr: private Allocator
{
    Obj_t* obj;

 public:
    // All the necessary public methods here
};

Now we are inheriting from Allocator, but we are saying that "IntrusivePtr is not an Allocator". We get all the optimization benefits of the inheritance trick, while still maintaining good object-oriented design. The IntrusivePtr class can access everything in Allocator as needed (and if a pointer to the allocator object is needed for whatever reason, the 'this' pointer can be cast to it), but the allocator doesn't mess up the public interface of the class.

Basically private inheritance is composition, but getting the technical benefits of inheritance (in this case empty base class optimization).