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.
  • 相关阅读:
    bert系列一:《Attention is all you need》论文解读
    维特比算法及python实现
    手写高斯混合聚类算法
    强化学习应用于游戏Tic-Tac-Toe
    EM算法分析
    手写LVQ(学习向量量化)聚类算法
    手写k-means算法
    tensorflow增强学习应用于一个小游戏
    Opencv之LBP特征(算法)
    手写朴素贝叶斯(naive_bayes)分类算法
  • 原文地址:https://www.cnblogs.com/zoneofmine/p/15347111.html
Copyright © 2011-2022 走看看