Effective C++, 2E | Item 42: Use private inheritance judiciously Back to Item 41: Differentiate between inheritance and templates. Continue to Item 43: Use multiple inheritance judiciously. 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 Students to Persons 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 inheritance: 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 then? "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 behavior. 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 moment. 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's member functions will be completely separate from the code making up Stack'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 thing. 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 by: Creating a single class that stores void* pointers to objects. Creating a set of additional classes whose only purpose is to enforce strong typing. These classes all use the generic class of step 1 for the actual work. Here's an example using the non-template Stack class of Item 41, except here it stores generic pointers instead of objects: 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 ints, and compilers would merrily accept it. After all, a pointer's a pointer when it comes to void* parameters. To regain the type safety to which you have become accustomed, you create interface classes to GenericStack, like this: class IntStack { // interface class for ints public: void push(int *intPtr) { s.push(intPtr); } int * pop() { return static_cast(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(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, nil. 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 prevent? Nothing. Nothing prevents that. But maybe something should. 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 functions: 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(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(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 functions. 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 inheritance: template class Stack: private GenericStack { public: void push(T *objectPtr) { GenericStack::push(objectPtr); } T * pop() { return static_cast(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 that. 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 remarkable. 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 can. Back to Item 41: Differentiate between inheritance and templates. Continue to Item 43: Use multiple inheritance judiciously.