《Effective C++》:条款34:区分接口继承和实现继承

public继承的概念,由2部分构成:函数接口(function Interface)继承和函数实现(function implementation)继承。这两种继承的差异有点像函数的声明和函数的定义之间的差异。

我们在设计class时,有时希望derived class只继承函数的接口(即函数声明);有时候希望derived class继承函数接口和实现,但又覆写它们所继承的实现;又有时候希望derived 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函数,所以它是个抽象类,不能创建Shape对象,但Shape强烈影响了所有以public继承它的derivedclass,因为

- 成员函数的接口总会被继承。条款32所说,public继承意味着is-a

Shape class有三个函数。draw是pure virtual函数;error是impure pure函数;objectID是non-virtual函数。

pure virtual函数有两个特点:它们必须被继承了它们的具体class重新声明,而且在抽象class中通常没有定义。这也就是说明:

  • 声明一个pure virtual函数的目的是为了让derived class只继承函数接口。

这也是合情合理的,因为Shape::draw并不知道我们要画什么图像,当然无法给出实现了。但是我们可以为pure virtual函数提供定义,即为Share::draw提供一份实现,C++不会发出怨言,但是调用这个函数的唯一途径是调用时指明其class名称:

 Shape* ps=new Shape;
 ps->draw();
 ps->Share::draw();

impure virtual函数和pure virtual函数有所不同,derived classes继承其函数接口,但impure virtual函数会提供一份实现代码,derived class可能覆写(override)它。

  • 声明简朴的(非纯)impure virtual函数的目的,是让derived classes继承该函数的接口和缺省实现。

考虑Shape::error这个例子,error接口表示,每个class都必须支持一个遇上错误时可调用的函数,但每个class可以自由处理错误。如果某个class不想针对错误做出特殊行为,可以退回到Shape class提供的缺省错误处理行为。也就是说Shape::error的声明式告诉derived class设计者:你必须支持一个error函数,但如果你不想自己写,可以使用Shape class提供的缺省版本。

如果允许impure virtual函数同时指定函数声明和函数缺省行为,有可能造成危险。考虑一个具体例子,一个XYZ航空公司设计飞机继承体系,该公司有A型和B型两种飞机,都以相同方式飞行,可以考虑这样设计继承体系:

    class Airport{ ……};
    class Airplane{
    public:
        virtual void fly(const Airport& destation);
        ……
    };
    void Airplane::fly(const Airport& destation)
    {
        //将飞机飞到指定的destination
    }
    class ModelA: public Airplane{……};
    class ModelB: public Airplane{……};

因为不同型飞机不需要不同的fly实现,Airplane::fly被声明为virtual;为了避免在ModelA和ModelB重新撰写相同代码,缺省的飞行行为有Airplane::fly提供。

上面这种设计方式是典型的面向对象设计。两个classes共享的性质放到base class中,然后被这两个class继承。这样可以突出共同性质,避免代码重复。

但是如果XYZ要购买一种新型飞机C,C和A、B飞行方式不同。XYZ公司程序员给C型飞机添加了一个class,但是没有重新定义fly函数

class ModelC: public Airplane{……};

然后又写了如下代码

    Airport PDX();//某个机场
    Airplane* pa=new ModelC;
    ……
    pa->fly(PDX);//调用了Airplane::fly

这会造成大灾难,因为程序员试图以ModelA或ModelB的方式来飞ModelC。问题不在于Airplane::fly有缺省行为,在于ModelC在未搞清楚的情况下就使用了这个缺省行为。幸运的是可以做到:提供缺省实现给derived classes,但是除非derived classes真的要用。这个做法是切断virtual函数接口和其缺省实现之间的连接。

    class Airplane{
    public:
        virtual void fly(const Airport& destation)=0;
    protected:
        void defaultFly(const Airport& destation);
    };
    void Airplane::deFaultFly(const Airport& destation)
    {
        //将飞机飞到指定目的地
    }

这里将Airplane::fly改为pure virtual函数,只提供接口。但是缺省的行为在Airplane::defaultFly函数中出现。如果要使用其缺省行为,可以在fly函数调用defaultFly函数。

    class ModelA: public Airplane{
    public:
        virtual void fly(const Airport& destation)
        { defaultFly(destation)}
    };
    class ModelB: public Airplane
    ……

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

上面设计中,Airplane::defaultFly是个non-virtual,derived classes不用重新定义(**条款**36)。如果Airplane::defaultFly是virtual函数,就会出现循环问题:万一derived classes忘记重新定义defaultFly函数会怎样?

有的人返回以不同的函数分别将提供接口和缺省实现,这样会因为过度雷同的函数名称引起class命名空间污染问题;但是他们同意接口和缺省实现应该分开。我们可以利用“pure virtual函数必须在derived classes中重新声明,但它们可以拥有自己的实现”这个特点

    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
    ……

    class ModelC: public Airplane
    {
    public:
        virtual void fly(const Airport& destination);
        ……
    };
    void ModelC::fly(const Airport& destination)
    {
        //ModelC的实现
    }

这个实现和上一个不同之处在于,用pure virtual函数Airplane::fly替换了独立函数Airplane::defaultFly。现在的fly被分割为两个基本要素:其声明部分表现为接口(derived classes必须使用),定义部分表现为缺省行为(derived classes明确提出申请才可以用)。

最后来看看Shape的non-virtual函数objectID;Shape::objectID是个non-virtual函数,这意味着它不打算在derived class中有不同行为。

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

可以把Shape::objectID看做“每个Shape对象都有一个用来产生识别码的函数,这个识别码采用相同计算方法。non-virtual函数代表的意义是不变性(invariant)凌驾特异性(specialization),所以不应该在derived classes中被重新定义,这个**条款**36讨论的重点。

pure virtual函数对应只继承接口;simple(impure) virtual函数对应继承接口和一份缺省实现;non-virtual函数对应继承接口和一份强制实现。在设计classes时,要分清这些区别和联系,否则容易犯两个错误:

  • 第一个错误是将所有函数声明为non-virtual。这会使derived classes没有空间进行特化工作;non-virtual函数会给析构函数带了问题(条款**7)。如果关心virtual函数的成本问题,可以参考条款**30的80-20法则。典型的程序有80%时间在执行20%代码,函数中有80%的virtual函数不一定会给程序带了多大效率损失,将心力花在20%代码上才是关键。
  • 第二个错误是将所有成员函数声明为virtual。有时候这样是正确的,例如**条款**31的Interface classes。如果有些函数在derived classes中不应该被重新定义,那么就应该将这些函数声明为non-virtual。

总结

  • 接口继承和实现继承不同。在public继承下,derived classes总是继承base classes的接口。
  • pure virtual函数只具体指定接口继承。
  • impure virtual函数具体指定接口继承和缺省实现继承。
  • non-virtual函数具体指定接口继承和强制性实现继承。
时间: 2024-10-05 23:37:07

《Effective C++》:条款34:区分接口继承和实现继承的相关文章

Effective C++ 条款34 区分接口继承和是实现继承

1. C++对于函数成员的继承主要有三种: 只继承接口,不继承实现; 同时继承接口和实现,同时允许覆写实现; 继承接口和实现,同时不允许对实现进行覆写. 2. 对于public继承,成员函数的接口应该总是被继承(由于is-a关系的存在),其中: pure-virtual函数的目的只是为了使派生类继承函数接口; impure-virtual(虚但非纯虚)函数的目的是既允许派生类继承接口和实现,又可以重写实现. non-virtual函数的目的是令派生类继承函数的接口以及一份强制实现.(如果想要重写

Effective C++ Item 34 区分接口继承与实现继承

本文为senlie原创,转载请保留此地址:http://blog.csdn.net/zhengsenlie 关联条款 Item 36 接口继承和实现继承不同.在 public 继承下, derived classes 总是继承 base class 的接口 class Shape{ public: virtual void draw() const = 0; virtual void error(const std::string &msg); int objectID() const; //.

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

作为类的设计者,有时希望派生类只继承成员函数的接口(声明):有时希望派生类同时继承函数的接口和实现,但允许派生类改写实现:有时则希望同时继承接口和实现,并且不允许派生类改写任何东西. class Shape { public: virtual void draw() const = 0; virtual void error(const string& msg); int objectID() const; ... }; class Rectangle: public Shape { ... }

Effective C++ 34 区分接口继承和实现继承

public继承从根本上讲,有两部分:接口继承和实现继承.两者之前的区别很像函数声明与函数定义. 具体设计中,会呈现三种形式:derived class只继承成员函数的接口(纯虚函数):derived class同时继承函数的接口和实现,同时能够重写(override):derived class同时继承函数的接口和实现,但是不允许重写该函数. 1.只继承成员函数的接口(纯虚函数): 例如pure函数 2.同时继承函数的接口和实现,同时能够重写(override): 例如impure函数,为了避

More Effective C++ 条款34 如何在一个程序中结合C++和C

1. C++和C混合使用的前提之一就是编译器产生兼容的目标文件(.lib和.dll等).所谓"兼容",指的是编译器在"预编译器相依的特性上"一致,如int和double大小,参数压栈机制等,只有在这个基础上才能讨论结合使用C++和C模块的问题. 2. 在1的基础上,要结合使用C++和C的模块,主要有以下几点需要注意: 1). name mangling(名称重整) Name mangling是C++用于支持函数重载的机制,它对重载的函数名称进行一定改变,使得每个函数

《Effective C++》之条款34:区分接口继承和实现继承

<Effective C++> 条款34:区分接口继承和实现继承 Public继承由两部分组成 函数接口继承 函数实现继承 三种可能面对的继承 derived class 只继承成员函数的接口 derived class 同时继承函数的接口和实现,但又希望能够覆写它们所继承的实现 derived class 同时继承函数的接口和实现,但不允许覆写任何东西 总结: 接口继承和实现继承不同.在public继承下,derived classes 总是继承base class 的接口. Pure vi

Effective C++:条款34:区分接口继承和实现继承

(一) 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 {...}; 公有继承的概念看似简单,似乎很轻易就浮出水面,然而仔细审度之后,我们会发现公有继承的概念实际上包含两个相互独立

条款34:区分接口继承和实现继承(Different between inheritance of interface and inheritance of implemenation)

NOTE: 1.接口继承和实现继承不同.在public继承之下,derived classes总是继承base class的接口. 2.pure virtual 函数只具体指定接口继承及缺省实现继承. 3.impure virtual 函数具体指定接口继承及缺省实现继承. 4.non-virtual 函数具体指定接口继承及强制实现继承.

Effective C++:规定34:区分接口继承和实现继承

(一个) 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 {...}; 公有继承的概念看似简单.似乎非常轻易就浮出水面.然而细致审度之后,我们会发现公有继承的概念实际上包括两个相互