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.
  • 相关阅读:
    Atitit 项目管理之时间管理之道 attilax著 艾龙 著 1. 项目活动的分解和定义 1 2. 第2章|项目活动定义与活动排序 13 1 3. 项目活动资源需求估计 2 4. 里程碑节点 2
    Atitit 算法之道 attilax著 1. 第二部分(Part II) 排序与顺序统计(Sorting and Order Statistics) 1 2. 第六章 堆排序(Heapsort)
    Atitit 依赖管理之道 1. 概念 依赖管理,是指在什么地方以什么形式引入外部代码。 1 1.1.1. 理解模块化和依赖管理: 1 1.2. 依赖管理,有三个层面。 单一职责原则,协议对象引用,
    Atitit 集成之道 attilax著 1. 所谓系统集成(SI,System Integration), 1 2. 发展方向 1 2.1. 产品技术服务型 2 2.2. 系统咨询型 2 2.3.
    Atitit ide之道 开发工具之道 attilax著 v2 s22.docx Atitit ide开发工具之道 attilax总结 1. 代码编辑器功能 3 1.1. 关键词颜色 3 1.2.
    Atitit 未来 技术趋势 没落技术 attilax著 艾龙 总结 1. 2018技术趋势 2 1.1. 人工智能与区块链 2 1.2. 2、 PWA 或将大热 2 1.3. 5、
    Atitit etl之道 attilax著 1. ETL 1 1.1. (数据仓库技术) 2 1.2. ETL的质量问题具体表现为正确性、完整性、一致性、完备性、有效性、时效性和可获取性等几个特性
    Atitit 微服务之道 attilax著 1. 什么是微服务架构? 1 1.1. 、微服务与SOA的关系 :微服务架架构师面向服务架构(SOA)的一种特定实现 2 1.2. 微服务与康威定律 2 1
    atitit 虚拟机之道vm之道 runtime设计 运行时 .attilax著.docx 1. Atitit 虚拟机的层次 架构与常见的虚拟机 3 1.1. Os隔离了硬件的区别 4 1.2.
    Atiitt 软件设计之道 attilax著 1. 总概念 隶属于软件工程。。 2 2. 需求分析 3 3. 设计分类 3 3.1. 按照力度 总体设计架构设计 概要设计 详细设计 3 3.2.
  • 原文地址:https://www.cnblogs.com/zoneofmine/p/15639444.html
Copyright © 2011-2022 走看看