Private inheritance means is-implemented-in-terms-of. It is usually inferior to composition, but it makes sense when a derived class needs access to protected base members or needs to redefine inherited virtual functions. For library developers who strive to minimize object sizes, it also offers the ability of empty base optimization.
The behavior of private inheritance is that
- Compilers will generally not convert a derived class object into a base class object
- members inherited from a private base class (protected or public ones) become private members of the derived class
With such behaviors, private inheritance means is-implemented-in-terms-of. Thus, private-inherited derived class D has no conceptual relationship with the base class B - private inheritance is purely an implementation technique in the implementation domain. Using the terms introduced in item 34:
private inheritance means to ignore interface, and only inherit implementation.
Compared to the composition, which also has the meaning of “is-implemented-in-terms-of”, the preference is simple:
use composition whenever we can, and use private inheritance whenever we must.
When must we?
- Primarily when we need to inherit protected members and/or virtual functions
- Sometimes when we want to take advance of empty base optimization (which is the edge case)
Typical usage
Let’s see a typical usecase for private inheritance. Suppose we want to periodically examine the the internal status of class Widget
, and we’d like to reuse the following utility class:
|
|
A Timer
object can be configured to tick with whatever frequency we need, and on each tick, it calls a virtual function, which we can redefine so that it examines the current state of the Widget
object. In order to redefine a virtual function, Widget
must inherit from Timer
. But public inheritance is inappropriate in this case: it’s not true that a Widget
is-a Timer
, because Widget
clients should not be able to call onTick
on a Widget
1.
Public inheritance is not a valid option here. Instead, we inherit privately:
|
|
After private inheritance, Timer
’s public onTick
function becomes private in Widget
, so keep it as private when we redeclare it2.
Alternatives
This is a nice design, but it’s worth noting that private inheritance isn’t strictly necessary - we could use composition instead:
|
|
This design is more complicated involving both (public) inheritance and composition, but we do get two more advantages from its complixity:
- Because
Widget
’s derived classes have no access to the privateWidgetTimer
data member, we prevent derived classes from redefiningonTick
3. - We’ve minimized
Widget
’s compilation dependencies: ifWidget
inherits fromTimer
,Timer
’s definition must be available whenWidget
is compiled, so we probably has to#include Timer.h
. IfWidgetTimer
is moved out ofWidget
andWidget
only contains a pointer to aWidgetTimer
, we only need to simply forward decalare theWidgetTimer
class without#include
anything. Such decouplings can be important for large systems (details in item 31).
Edge case
The edge case, as the name suggests, is edgy indeed: it applies only when we’re dealing with a class that has no data in it: no non-static data members; no virtual functions (which introduces vptr
to each object, item 7); and no virual base classes (which also incurs a size overhead, item 40). Conceptually, such an empty class should use no space, but C++ requires that freestanding objects must have non-zero size for some technical reasons. That being said,
|
|
We’ll find that sizeof(HoldsAnInt) > sizeof(int)
, so Empty
data member does require extra memory. With most compilers, sizeof(Empty)
is 1, which is a silently inserted char
inside the Empty
objects to satisfy C++’s requirements. However, alignment requirements (see item 50) may casue compilers to add padding to HoldsAnInt
classes, so the objects of HoldsAnInt
may gain more than just the size of a char - they would actually enlarge enough to hold a second int.
Now comes the point of using private inheritance: rather than containing “freestanding” objects that can not have zero size, we could inherit from Empty
type:
|
|
Now sizeof(HoldsAnInt) == sizeof(int)
, thanks to empty base optimization (EBO), which is typically supported by most compilersp4.
In practice, “empty” base classes often contain typedefs, enums, static data members, or non-virtual functions, such as those in the STL that contains useful members (usually typedefs), and thus classes for user-defined function objects may inherit from them without worrying about size increase thanks to EBO.
In summary
Private inheritance is most likely to be a legitimate design strategy when we’re dealing with two classes not related by is-a where one either needs access to the protected members of another or needs to redefine one or more of its virtual functions. Even in this case, however, a mixture of public inheritance and composition can often yield the expected behavior, albeit with more design complexity.
Most classes aren’t empty, so the EBO is rarely a legitimate justification for private inheritance.
Using private inheritance judiciously means employing it when, having considered all the alternatives, it’s the best way to express the relationship between two classes.
-
Allowing adding
onTick
into the conceptualWidget
interface would make it easy for clients to use theWidget
interface incorrectly, a clear violation of item 18’s advice to make interfaces easy to use correctly and hard to use incorrectly. ↩︎ -
Putting
onTick
in the public side will not change its accessibility, only to mislead clients into thinking they could call it, and this, again, violates item 18’s advice. ↩︎ -
If
Widget
inherits directly fromTimer
,Widget
’s derived classes may redefineonTick
even ifonTick
is private (recall item 35 that derived classes may redefine virtual functions even if they are not permitted to call them). By the way, Java or C# would not have such trouble due to their keywordfinal
orsealed
. ↩︎ -
It is worth knowing that the EBO is generally viable only under insgle inheritance, and can’t be applied to derived classes taht have more than one base. ↩︎