Effective C++ 笔记 —— Item 39: Use private inheritance judiciously.

Item 32 demonstrates that C++ treats public inheritance as an is-a 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 : private Person { /*...*/ }; // inheritance is now private

void eat(const Person& p); // anyone can eat
void study(const Student& s); // only students study

Person p; // p is a Person
Student s; // s is a Student

eat(p); // fine, p is a Person
eat(s); // error! a Student isn't a Person

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 eat fails for the objects.

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.

Using the terms introduced in Item 34, 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 38 points out that composition can mean the same thing. How are you supposed to choose between them?

The answer is simple: use composition whenever you can, and use private inheritance whenever you must.

When must you? Primarily when protected members and/or virtual functions enter the picture, though there's also an edge case where space concerns can tip the scales toward private inheritance. We'll worry about the edge case later. After all, it's an edge case.

Suppose not only do we want to know things like how often Widget member functions are called, we also want to know how the call ratios change over time. 

Preferring to reuse existing code over writing new code, we rummage around in our utility toolkit and are pleased to find the following class:

class Timer 
{
public:
    explicit Timer(int tickFrequency);
     virtual void onTick() const; // automatically called for each tick
    // ...
};

In order for Widget to redefine a virtual function in Timer, Widget must inherit from Timer. But public inheritance is inappropriate in this case. It's not true that a Widget is-a Timer. Widget clients shouldn't be able to call onTick on a Widget, because that's not part of the conceptual Widget interface. Allowing such a function call would make it easy for clients to use the Widget interface incorrectly, a clear violation of Item 18's advice to make interfaces easy to use correctly and hard to use incorrectly. Public inheritance is not a valid option here.

We thus inherit privately:

class Widget: private Timer 
{
private:
    virtual void onTick() const; // look at Widget usage data, etc.
    // ...
};

This is a nice design, but it's worth noting that private inheritance isn't strictly necessary. If we were determined to use composition instead, we could. We'd just declare a private nested class inside Widget that would publicly inherit from Timer, redefine onTick there, and put an object of that type inside Widget. Here's a sketch of the approach:

class Widget 
{
private:
    class WidgetTimer: public Timer 
    {
    public:
        virtual void onTick() const;
        // ...
    };

     WidgetTimer timer;
    // ...
};

 I remarked earlier that there was an edge case involving space optimization that could nudge you to prefer private inheritance over composition.

The edge case is edgy indeed: it applies only when you're dealing with a class that has no data in it. Such classes have no non-static data members; no virtual functions (because the existence of such functions adds a vptr to each object — see Item 7); and no virtual base classes (because such base classes also incur a size overhead — see Item 40). Conceptually, objects of such empty classes should use no space, because there is no per-object data to be stored. However, there are technical reasons for C++ decreeing that freestanding objects must have non-zero size, so if you do this:

class Empty {};     // has no data, so objects should

// use no memory
class HoldsAnInt 
{ 
// should need only space for an int
private:
    int x;
    Empty e; // should require no memory
};

you'll find that sizeof(HoldsAnInt) > sizeof(int); an Empty data member requires memory. With most compilers, sizeof(Empty) is 1, because C++'s edict against zero-size freestanding objects is typically satisfied by the silent insertion of a char into "empty" objects. However, alignment requirements (see Item 50) may cause compilers to add padding to classes like HoldsAnInt, so it's likely that HoldsAnInt objects wouldn't gain just the size of a char, they would actually enlarge enough to hold a second int. (On all the compilers I tested, that's exactly what happened.)

But perhaps you've noticed that I've been careful to say that "freestanding" objects mustn't have zero size. This constraint doesn't apply to base class parts of derived class objects, because they're not freestanding. If you inherit from Empty instead of containing an object of that type:

class HoldsAnInt: private Empty 
{
private:
    int x;
};

you're almost sure to find that sizeof(HoldsAnInt) == sizeof(int). This is known as the empty base optimization (EBO), and it's implemented byall the compilers I tested. If you're a library developer whose clientscare about space, the EBO is worth knowing about. Also worth knowing is that the EBO is generally viable only under single inheritance. The rules governing C++ object layout generally mean that the EBO can't be applied to derived classes that have more than one base. 

In practice, “empty” classes aren't truly empty. Though they never have non-static data members, they often contain typedefs, enums, static data members, or non-virtual functions. The STL has many technically empty classes that contain useful members (usually typedefs), including the base classes unary_function and binary_function, from which classes for user-defined function objects typically inherit. Thanks to widespread implementation of the EBO, such inheritance rarely increases the size of the inheriting classes.

Private inheritance is most likely to be a legitimate design strategy when you'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 that case, we've seen that a mixture of public inheritance and containment can often yield the behavior you want, albeit with greater design complexity. 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 in your software.

Things to Remember:

  • Private inheritance means is-implemented-in-terms of. It's usually inferior to composition, but it makes sense when a derived class needs access to protected base class members or needs to redefine inherited virtual functions.
  • Unlike composition, private inheritance can enable the empty base optimization. This can be important for library developers who strive to minimize object sizes.
原文地址:https://www.cnblogs.com/zoneofmine/p/15639444.html