zoukankan      html  css  js  c++  java
  • Effective C++ 笔记 —— Item 34: Differentiate between inheritance of interface and inheritance of implementation.

    The seemingly straightforward notion of (public) inheritance turns out, upon closer examination, to be composed of two separable parts: inheritance of function interfaces and inheritance of function implementations.

    Consider a class:

    class Shape 
    {
    public:
        virtual void draw() const = 0;
        virtual void error(const std::string& msg);
        int objectID() const;
        // ...
    };
    
    class Rectangle: public Shape { /*...*/ };
    class Ellipse: public Shape { /*...*/ };

    The purpose of declaring a pure virtual function is to have derived classes inherit a function interface only.

    Incidentally, it is possible to provide a definition for a pure virtual function.That is, you could provide an implementation for Shape::draw, and C++ wouldn't complain, but the only way to call it would be to qualify the call with the class name:

    Shape *ps = new Shape; // error! Shape is abstract
    Shape *ps1 = new Rectangle; // fine
    ps1->draw(); // calls Rectangle::draw
    Shape *ps2 = new Ellipse; // fine
    ps2->draw(); // calls Ellipse::draw
    ps1->Shape::draw(); // calls Shape::draw
    ps2->Shape::draw(); // calls Shape::draw

    The purpose of declaring a simple virtual function is to have derived classes inherit a function interface as well as a default implementation.

    class Shape 
    {
    public:
        virtual void error(const std::string& msg);
        // ...
    };

    It turns out that it can be dangerous to allow simple virtual functions to specify both a function interface and a default implementation.

    class Airport { /*...*/ }; // represents airports
    
    class Airplane
    {
    public:
        virtual void fly(const Airport& destination);
        // ...
    };
    
    void Airplane::fly(const Airport& destination)
    {
        // default code for flying an airplane to the given destination
    }
    
    class ModelA: public Airplane { /*...*/ };
    class ModelB: public Airplane { /*...*/ };

    But what if you forget to redefine the fly function:

    class ModelC: public Airplane 
    {
        // ... 
        // no fly function is declared
    };

    In their code, then, they have something akin to the following:

    Airport PDX( /*...*/ ); // PDX is the airport near my home
    Airplane *pa = new ModelC;
    // ...
    pa->fly(PDX); // calls Airplane::fly!

    The problem here is not that Airplane::fly has default behavior, but that ModelC was allowed to inherit that behavior without explicitly saying that it wanted to. Fortunately, it's easy to offer default behavior to derived classes but not give it to them unless they ask for it. The trick is to sever the connection between the interface of the virtual function and its default implementation. Here's one way to do it:

    class Airplane 
    {
    public:
        virtual void fly(const Airport& destination) = 0;
        // ...
    protected:
        void defaultFly(const Airport& destination);
    };
    
    void Airplane::defaultFly(const Airport& destination)
    {
        // default code for flying an airplane to the given destination
    }

    Notice how Airplane::fly has been turned into a pure virtual function. That provides the interface for flying. The default implementation is also present in the Airplane class, but now it's in the form of an independent function, defaultFly. Classes like ModelA and ModelB that want to use the default behavior simply make an inline call to defaultFly inside their body of fly (but see Item 30 for information on the interaction of inlining and virtual functions):

    class ModelA: public Airplane 
    {
    public:
        virtual void fly(const Airport& destination){ defaultFly(destination); }
        // ...
    };
    
    class ModelB: public Airplane 
    {
    public:
        virtual void fly(const Airport& destination){ defaultFly(destination); }
        // ...
    };

    For the ModelC class, there is no possibility of accidentally inheriting the incorrect implementation of fly, because the pure virtual in Airplane forces ModelC to provide its own version of fly.

    class ModelC: public Airplane 
    {
    public:
        virtual void fly(const Airport& destination);
        // ...
    };
    void ModelC::fly(const Airport& destination)
    {
        // code for flying a ModelC airplane to the given destination
    }

    Some people object to the idea of having separate functions for providing interface and default implementation, such as fly and defaultFly above. For one thing, they note, it pollutes the class namespace with a proliferation of closely related function names. Yet they still agree that interface and default implementation should be separated. How do they resolve this seeming contradiction? By taking advantage of the fact that pure virtual functions must be redeclared in concrete derived classes, but they may also have implementations of their own. Here’s how the Airplane hierarchy could take advantage of the ability to define a pure virtual function:

    class Airplane 
    {
    public:
        virtual void fly(const Airport& destination) = 0;
        // ...
    };
    
    void Airplane::fly(const Airport& destination) // an implementation of a pure virtual function
    { 
        // default code for flying an airplane to the given destination
    }
    
    class ModelA: public Airplane 
    {
    public:
        virtual void fly(const Airport& destination) { Airplane::fly(destination); }
        // ...
    };
    
    class ModelB: public Airplane 
    {
    public:
        virtual void fly(const Airport& destination) { Airplane::fly(destination); }
        // ...
    };
    
    class ModelC: public Airplane 
    {
    public:
        virtual void fly(const Airport& destination);
        // ...
    };
    
    void ModelC::fly(const Airport& destination)
    {
        // code for flying a ModelC airplane to the given destination
    }

    When a member function is non-virtual, it’s not supposed to behave differently in derived classes. In fact, a non-virtual member function specifies an invariant over specialization, because it identifies behavior that is not supposed to change, no matter how specialized a derived class becomes. As such:

    • The purpose of declaring a non-virtual function is to have derived classes inherit a function interface as well as a mandatory implementation.

    Things to Remember:

    • Inheritance of interface is different from inheritance of implementation. Under public inheritance, derived classes always inherit base class interfaces.
    • Pure virtual functions specify inheritance of interface only.
    • Simple (impure) virtual functions specify inheritance of interface plus inheritance of a default implementation.
    • Non-virtual functions specify inheritance of interface plus inheritance of a mandatory implementation.
  • 相关阅读:
    centos7 安装openGauss极简版本
    postgresql 通过一个表创建一个新表
    postgresql字符串函数与操作符
    SQLServer查看各个表大小
    seata1.3 分布式事务集成 AT模式
    用户体验——以用户为中心的Web设计_Chapter1. 用户体验为什么如此重要
    用户体验——以用户为中心的Web设计_Chapter2. 认识这些要素
    用户体验——以用户为中心的Web设计_Chapter3. 战略层:网站目标和用户需求
    用户体验——以用户为中心的Web设计_Chapter4. 范围层:功能规格和内容需求
    lineheight 详解,及个别问题
  • 原文地址:https://www.cnblogs.com/zoneofmine/p/15347111.html
Copyright © 2011-2022 走看看