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.
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?
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?
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).