zoukankan      html  css  js  c++  java
  • effective C++ 条款 34:区分接口继承和实现继承

    身为class设计者,有时候你希望derived class只继承成员函数的接口(也就是声明):有时候你又希望derived class同时继承函数的

    接口和实现,但又希望能够覆写(override)它们所继承的实现:又有时候你希望derived class同时继承函数的接口与实现,并且不允许覆写任何东西。

    让我们考虑一个展现绘图程序中各种几何形状的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 {...};

    Shape的pure virtual函数draw使它成为一个抽象class。Shape强烈影响了所有以public形式继承它的derived classes。因为:

    成员函数的接口总是会被继承

    首先考虑pure virtual函数draw:pure virtual函数有两个最突出的特性:它们必须被任何“继承了它们”的具象class重新声明,而且它们在抽象class中通常没有定义。所以:

    声明一个pure virtual函数的目的是为了让derived class只继承函数接口。Shape::draw的声明式乃是对具象derived class设计者说“你必须提供一个draw函数,但我不干涉你怎么实现它”

    令人意外的是,我们竟然可以为pure virtual函数提供定义。但调用它的唯一途径是“调用时明确指出其class名称”:

    Shape* ps = new Shape; //wrong! abstract class Shape
    Shape* ps1 = new Rectangle;
    ps1->draw(); //Rectangle::draw()
    Shape* ps2 = new Ellipse;
    ps2->draw(); //Ellipse::draw()
    ps1->Shape::draw();
    ps2->Shape::draw();

    它可以提供一种机制,为简朴impure virtual函数提更平常更安全的缺省实现。

    impure virtual函数会提供一份实现代码,derived classes可能覆写(override)它:

    生命简朴的(非纯)impure virtual函数的目的,是让derived class继承该函数的接口和缺省实现

    考虑Shape::error这个例子。

    其接口表示,每个class都必须支持一个“当遇上错误时可调用”的函数,但每个class可自由处理错误。如果某个class不想针对错误作出任何特殊行为,可以退回到Shape class提供的缺省错误处理行为。

    但是允许impure virtual函数同时指定函数声明和函数缺省行为,却有可能造成危险。考虑xyz航空公司设计的继承体系。该公司只有A和B两种飞机,相同方式飞行:

    class Airport {...};
    class Airplane {
    public:
        virtual void fly(const Airport& destination);
        ...
    };
    void Airplane::fly(const Airport& destination)
    {
        //缺省代码,将飞机飞至指定目的地
    }
    class ModelA: public Airplane {...};
    class ModelB: public Airplane {...};

    为表示所有飞机都一定能飞,并阐明“不同型飞机原则上需要不同的fly实现”Airplane::fly被声明为virtual。然而为了避免在ModelA和ModelB中撰写相同代码,缺省飞行行为有Airplane::fly提供,它同时被ModelA和ModelB继承。

    现在,xyz决定购买一种新式C型飞机,这种飞机的飞行方式不同。

    xyz的程序员在继承体系中对C型飞机添加了一个class,但由于他们急着让新飞机上线服务,忘了重新定义器fly函数:

    class ModelC: public Airplane {...};

    然后在代码中有一些诸如此类的动作:

    Airport PDX(...);
    Airplane* pa = new ModelC;
    ...
    pa->fly(PDX);

    这将酿成大灾难:这个程序试图用ModelA和ModelB的飞行方式来飞ModelC。

    问题不在Airplane::fly()有缺省行为,而在于ModelC在未明白说出“我要”的情况下就继承了该缺省行为。我们可以做到“提供缺省实现给derived classes,但除非它们明确要求,否则免谈”。此间伎俩在于切断“virtual 函数接口”和其“缺省实现”之间的连接。下面是一种做法:

    class Airplane {
    public:
        virtual void fly(const Airport& destination) = 0;
        ...
    protected:
        void defaultFly(const Airport& destination);
    };
    void Airplane::defaultFly(const Airport& destination)
    {
        //缺省行为,将飞机飞至目的地
    }

    fly已被改成为一个pure virtual函数,只提供飞行接口。缺省行为以defaultFly出现在Airplane class中。若想使用缺省实现(例如ModelA和ModelB),可以在fly中对defaultFly做一个inline调用:

    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中的pure virtual函数迫使ModelC必须提供自己的fly版本:

    class ModelC: public Airplane {
    public:
        virtual void fly(const Airport& destination);
        ...
    };
    void ModelC::fly(const Airport& destination)
    {
        //将C型飞机飞至指定的目的地
    }

    这个方案并非安全无虞,程序员还是可能因为剪贴(copy-and-paste)代码而招来麻烦,但它比原来的设计值得依赖。

    Airplane::defaultFly是一个non-virtual函数,这一点也很重要。因为没有任何一个derived class应该重新定义此函数。

    有些人反对以不同的函数分别提供接口和缺省实现,向上述的fly和defaultFly那样。我们可以利用“pure virtual函数必须在derived class中重新声明,但它们可以拥有自己的实现”这一事实。下面是Airplane继承体系如何给pure virtual函数一份定义:

    class Airplane {
    public:
        virtual void fly(const Airport& destination) = 0;
        ...
    };
    void Airplane::fly(const Airport& destination) // pure virtual 函数实现
    {
        //缺省行为,将飞机飞至指定目的地
    }
    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)
    {
        // 将C型飞机飞至指定目的地
    }

    这几乎和前一个设计一模一样,只不过pure virtual函数Airplane::fly替换了独立函数Airplane::defaultFly。其声明部分表现表的是接口(derived class必须使用的),其定义部分表现出缺省行为(那是derived class可能使用的,但只有在它们明确提出申请时才是)。合并fly和defaultFly,就丧失了“让两个函数享有不同保护级别”的机会:习惯上被设为protected的函数(defaultFly)如今成了public(因为它在fly之中)。

    如果成员函数是个non-virtual函数,意味着它并不打算在derived classes中有不同的行为。non-virtual 成员函数所表现的不变性凌驾其特异性,无论derived class变得多么特异化,它的行为都不可以改变。

    声明non-virtual函数的目的是为了令derived class继承函数的接口及一份强制性实现

    来看Shape::objectID的声明:可以想做是“每个Shape对象都有一个用来产生对象识别码的函数:此识别码总是采用相同计算方法,该方法由Shape::objectID的定义式决定,任何derived class都不应该尝试改变其行为”。

  • 相关阅读:
    Windows Store App 主题动画
    Windows Store App 过渡动画
    Windows Store App 控件动画
    Windows Store App 近期访问列表
    Windows Store App 文件选取器
    Windows Store App 访问应用内部文件
    Windows Store App 用户库文件分组
    Windows Store App 获取文件及文件夹列表
    Windows Store App 用户库文件夹操作
    Windows Store App 用户库文件操作
  • 原文地址:https://www.cnblogs.com/lidan/p/2345324.html
Copyright © 2011-2022 走看看