Item 42: Use private inheritance judiciously.
Item 35 demonstrates that C++ treats public inheritance as an isa relationship. It does this by showing that compilers, when given a hierarchy in which a class Student
publicly inherits from a class Person
, implicitly convert Student
s to Person
s when that is necessary for a function call to succeed. It's worth repeating a portion of that example using private inheritance instead of public
class Person { ... };
class Student: // this time we use private Person { ... }; // private inheritance
void dance(const Person& p); // anyone can dance
void study(const Student& s); // only students study
Person p; // p is a Person Student s; // s is a Student
dance(p); // fine, p is a Person
dance(s); // error! a Student isn't // a Person
Clearly, private inheritance doesn't mean isa. What does it mean
"Whoa!" you say. "Before we get to the meaning, let's cover the behavior. How does private inheritance behave?" Well, the first rule governing private inheritance you've just seen in action: in contrast to public inheritance, compilers will generally not convert a derived class object (such as Student
) into a base class object (such as Person
) if the inheritance relationship between the classes is private. That's why the call to dance
fails for the object s
. The second rule is that members inherited from a private base class become private members of the derived class, even if they were protected or public in the base class. So much for
That brings us to meaning. Private inheritance means is-implemented-in-terms-of. If you make a class D
privately inherit from a class B
, you do so because you are interested in taking advantage of some of the code that has already been written for class B
, not because there is any conceptual relationship between objects of type B
and objects of type D
. As such, private inheritance is purely an implementation technique. Using the terms introduced in Item 36, private inheritance means that implementation only should be inherited; interface should be ignored. If D
privately inherits from B
, it means that D
objects are implemented in terms of B
objects, nothing more. Private inheritance means nothing during software design, only during software implementation.
The fact that private inheritance means is-implemented-in-terms-of is a
little disturbing, because Item
40 points out that layering can mean the same thing. How are you supposed
to choose between them? The answer is simple: use layering whenever you can,
use private inheritance whenever you must. When must you? When protected
members and/or virtual functions enter the picture but more on that in
a
Item 41 shows a way to write a Stack
template
that generates classes holding objects of different types. You may wish to
familiarize yourself with that Item now. Templates are one of the most useful
features in C++, but once you start using them regularly, you'll discover
that if you instantiate a template a dozen times, you are likely to
instantiate the code for the template a dozen times. In the case of
the Stack
template, the code making up
Stack<int>
's member functions will be completely separate from the code making up
Stack<double>
's member functions. Sometimes this is
unavoidable, but such code replication is likely to exist even if the
template functions could in fact share code. There is a name for the
resultant increase in object code size: template-induced code bloat.
It is not a good
For certain kinds of classes, you
can use generic pointers to avoid it. The classes to which this approach is
applicable store pointers instead of objects, and they are implemented
void*
pointers
to objects.
Here's an example using the non-template
Stack
class of Item
41, except here it stores generic pointers instead of
class GenericStack { public: GenericStack(); ~GenericStack();
void push(void *object); void * pop();
bool empty() const;
private: struct StackNode { void *data; // data at this node StackNode *next; // next node in list
StackNode(void *newData, StackNode *nextNode) : data(newData), next(nextNode) {} };
StackNode *top; // top of stack
GenericStack(const GenericStack& rhs); // prevent copying and GenericStack& // assignment (see operator=(const GenericStack& rhs); // Item 27) };
Because this class stores pointers instead of objects, it is possible that an
object is pointed to by more than one stack (i.e., has been pushed onto
multiple stacks). It is thus of critical importance that pop
and
the class destructor not delete the data
pointer of any
StackNode
object they destroy, although they must continue to
delete the StackNode
object itself. After all, the
StackNode
objects are allocated inside the
GenericStack
class, so they must also be deallocated inside that
class. As a result, the implementation of the Stack
class in Item 41 suffices almost perfectly
for the GenericStack
class. The only changes you need to make
involve substitutions of void*
for T
.
The GenericStack
class by itself is of little utility it's too easy to misuse. For example, a client could mistakenly push a pointer to a Cat
object onto a stack meant to hold only pointers to int
s, and compilers would merrily accept it. After all, a pointer's a pointer when it comes to void*
To regain the type safety to which you have become accustomed, you create interface classes to GenericStack
, like
class IntStack { // interface class for ints public: void push(int *intPtr) { s.push(intPtr); } int * pop() { return static_cast<int*>(s.pop()); } bool empty() const { return s.empty(); }
private: GenericStack s; // implementation };
class CatStack { // interface class for cats public: void push(Cat *catPtr) { s.push(catPtr); } Cat * pop() { return static_cast<Cat*>(s.pop()); } bool empty() const { return s.empty(); }
private: GenericStack s; // implementation };
As you can see, the IntStack
and CatStack
classes
serve only to enforce strong typing. Only int
pointers can be
pushed onto an IntStack
or popped from it, and only
Cat
pointers can be pushed onto a CatStack
or
popped from it. Both IntStack
and CatStack
are
implemented in terms of the class GenericStack
, a relationship
that is expressed through layering (see Item 40), and IntStack
and
CatStack
will share the code for the functions in
GenericStack
that actually implement their behavior.
Furthermore, the fact that all IntStack
and
CatStack
member functions are (implicitly) inline
means that the runtime cost of using these interface classes is zip, zero,
nada,
But what if potential clients
don't realize that? What if they mistakenly believe that use of
GenericStack
is more efficient, or what if they're just wild and
reckless and think only wimps need type-safety nets? What's to keep them from
bypassing IntStack
and CatStack
and going straight
to GenericStack
, where they'll be free to make the kinds of type
errors C++ was specifically designed to
Nothing. Nothing prevents that. But maybe something
I mentioned at the outset of this Item that an alternative way to assert an is-implemented-in-terms-of relationship between classes is through private inheritance. In this case, that technique offers an advantage over layering, because it allows you to express the idea that GenericStack
is too unsafe for general use, that it should be used only to implement other classes. You say that by protecting GenericStack
's member
class GenericStack { protected: GenericStack(); ~GenericStack();
void push(void *object); void * pop();
bool empty() const;
private: ... // same as above };
GenericStack s; // error! constructor is // protected
class IntStack: private GenericStack { public: void push(int *intPtr) { GenericStack::push(intPtr); } int * pop() { return static_cast<int*>(GenericStack::pop()); } bool empty() const { return GenericStack::empty(); } };
class CatStack: private GenericStack { public: void push(Cat *catPtr) { GenericStack::push(catPtr); } Cat * pop() { return static_cast<Cat*>(GenericStack::pop()); } bool empty() const { return GenericStack::empty(); } };
IntStack is; // fine
CatStack cs; // also fine
Like the layering approach, the implementation based on private inheritance avoids code duplication, because the type-safe interface classes consist of nothing but inline calls to the underlying GenericStack
Building type-safe interfaces on top of the GenericStack
class is a pretty slick maneuver, but it's awfully unpleasant to have to type in all those interface classes by hand. Fortunately, you don't have to. You can use templates to generate them automatically. Here's a template to generate type-safe stack interfaces using private
template<class T> class Stack: private GenericStack { public: void push(T *objectPtr) { GenericStack::push(objectPtr); } T * pop() { return static_cast<T*>(GenericStack::pop()); } bool empty() const { return GenericStack::empty(); } };
This is amazing code, though you may not realize it right away. Because of the template, compilers will automatically generate as many interface classes as you need. Because those classes are type-safe, client type errors are detected during compilation. Because GenericStack
's member functions are protected and interface classes use it as a private base class, clients are unable to bypass the interface classes. Because each interface class member function is (implicitly) declared inline
, no runtime cost is incurred by use of the type-safe classes; the generated code is exactly the same as if clients programmed with GenericStack
directly (assuming compilers respect the inline
request see Item 33). And because GenericStack
uses void*
pointers, you pay for only one copy of the code for manipulating stacks, no matter how many different types of stack you use in your program. In short, this design gives you code that's both maximally efficient and maximally type safe. It's difficult to do better than
One of the precepts of this book is that C++'s features interact in remarkable ways. This example, I hope you'll agree, is pretty
The insight to carry away from this example is that it could not have been achieved using layering. Only inheritance gives access to protected members, and only inheritance allows for virtual functions to be redefined. (For an example of how the existence of virtual functions can motivate the use of private inheritance, see Item 43.) Because virtual functions and protected members exist, private inheritance is sometimes the only practical way to express an is-implemented-in-terms-of relationship between classes. As a result, you shouldn't be afraid to use private inheritance when it's the most appropriate implementation technique at your disposal. At the same time, however, layering is the preferable technique in general, so you should employ it whenever you