• 条款36: 区分接口继承和实现继承


    作为类的设计者,有时希望派生类只继承成员函数的接口(声明);有时希望派生类同时继承函数的接口和实现,但允许派生类改写实现;有时则希望同时继承接口和实现,并且不允许派生类改写任何东西。

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

    首先看纯虚函数draw。纯虚函数最显著的特征是:它们必须在继承了它们的任何具体类中重新声明,而且它们在抽象类中往往没有定义。把这两个特征放在一起,就会认识到:

    · 定义纯虚函数的目的在于,使派生类仅仅只是继承函数的接口。

    为一个纯虚函数提供定义也是可能的。也就是说,你可以为Shape::draw提供实现,C++编译器也不会阻拦,但调用它的唯一方式是通过类名完整地指明是哪个调用:

    Shape *ps = new Shape;           // 错误! Shape是抽象的,new会调用类的构造函数,实例化对象,显然不行
    
    Shape *ps1 = new Rectangle;      // 正确
    ps1->draw();                     // 调用Rectangle::draw
    
    Shape *ps2 = new Ellipse;        // 正确
    ps2->draw();                     // 调用Ellipse::draw
    
    ps1->Shape::draw();              // 调用Shape::draw
    
    ps2->Shape::draw();              // 调用Shape::draw

    Shape::draw();           //出错,非静态成员引用必须与特定对象相对

    有时,声明一个除纯虚函数外什么也不包含的类很有用。这样的类叫协议类(Protocol class),它为派生类仅提供函数接口,完全没有实现。协议类在条款34中介绍过,并将在条款43再次提及。

    声明简单虚函数的目的在于,使派生类继承函数的接口和缺省实现。

    基类为子类提供缺省行为、同时只是在子类想要的时候才给它们的实现:切断虚函数的接口和它的缺省实现之间的联系

    1.基类函数声明为纯虚函数,同时缺省实现定义在另一个函数中,若派生类想使用基类的缺省实现,则在继承而来的纯虚函数中显式调用缺省实现函数

    2.基类函数声明为纯虚函数,同时基类中为该纯虚函数提供定义,若派生类想使用基类的缺省实现,则在继承而来的纯虚函数中显式调用基类中该函数(类名调用)

    方法一:

    class Airplane {
    public:
      virtual void fly(const Airport& destination) = 0;
    
      ...
    
    protected:
      void defaultFly(const Airport& destination);
    };
    
    void Airplane::defaultFly(const Airport& destination)
    {
      飞机飞往某一目的地的缺省代码
    }

    注意Airplane::fly已经变成了纯虚函数,它提供了飞行的接口。缺省实现还是存在于Airplane类中,但现在它是以一个独立函数(defaultFly)的形式存在的。ModelA和ModelB这些类想执行缺省行为的话,只用简单地在它们的fly函数体中对defaultFly进行一个内联调用

    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); }
    
      ...
    
    };

    对于ModelC类来说,它不可能无意间继承不正确的fly实现。因为Airplane中的纯虚函数强迫ModelC提供它自己版本的fly。

    class ModelC: public Airplane {
    public:
      virtual void fly(const Airport& destination);
      ...
    
    };
    
    void ModelC::fly(const Airport& destination)
    {
      ModelC飞往某一目的地的代码
    }

    方法二:纯虚函数必须在子类中重新声明,但它还是可以在基类中有自己的实现

    class Airplane {
    public:
      virtual void fly(const Airport& destination) = 0;
    
      ...
    
    };
    
    void Airplane::fly(const Airport& 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)
    {
      ModelC飞往某一目的地的代码
    }

    声明非虚函数的目的在于,使派生类继承函数的接口和强制性实现。当一个成员函数为非虚函数时,它在派生类中的行为就不应该不同。若派生类又重定义了该函数,则会隐藏基类的该函数。实际上,非虚成员函数表明了一种特殊性上的不变性,因为它表示的是不会改变的行为 ---- 不管一个派生类有多特殊。

  • 相关阅读:
    MTK Android 源码目录分析
    MTK Android 平台语言支持状态
    开坑了啦啦啦..
    codeforces泛做..
    用介个新的blog咯..
    【UR #5】怎样跑得更快
    【UR #5】怎样提高智商
    【集训队互测2016】消失的源代码
    口胡
    [八省联考2018]劈配
  • 原文地址:https://www.cnblogs.com/ljygoodgoodstudydaydayup/p/3924448.html
Copyright © 2020-2023  润新知