zoukankan      html  css  js  c++  java
  • 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.
  • 相关阅读:
    How to: Implement a View Item 如何:实现视图项
    How to:Create a New Object using the Navigation Control 如何:使用导航控件创建新对象
    How to:Access the Transition Manager 如何:访问过渡管理器
    How to: Implement Custom Context Navigation 如何:实现自定义上下文导航
    How to: Access Master Detail View and Nested List View Environment 如何:访问主详细信息视图和嵌套列表视图环境
    How to: Display a Detail View Directly in Edit Mode in ASP.NET Applications 如何:在ASP.NET应用程序中直接在编辑模式下显示详细信息视图
    How to: Detect a Lookup List View in Code 如何:在代码中检测查找列表视图
    How to: Create and Show a Detail View of the Selected Object in a Popup Window 如何:在弹出窗口中创建和显示选定对象的详细信息视图
    How to: Access Objects Selected in the Current View 如何:访问在当前视图中选择的对象
    How to: Display a List View as a Pivot Grid Table and Chart如何:将列表视图显示为数据透视网格表和图表
  • 原文地址:https://www.cnblogs.com/zoneofmine/p/15639444.html
Copyright © 2011-2022 走看看