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

公有继承的概念看似简单,似乎很轻易就浮出水面,然而仔细审度之后,我们会发现公有继承的概念实际上包含两个相互独立的部分:函数接口的继承和函数实现的继承。二者之间的差别恰与函数声明和函数实现之间相异之处等价。成员函数的接口总是被继承,因为public继承意味is-a。

pure virtual 函数两个突出特征:(1)必须被任何“继承了它们”的具象class重新声明;(2)它们在抽象class中通常没有定义。

(二)

声明一个pure
virtual函数的目的是为了让derived class只继承函数接口。我可以为它提供一份实现代码,但调用它的唯一途径是“调用时明确指出期class的名称”。

Sharp* ps1 = new Rectangle;
ps1->draw();
Sharp* ps2 = new Rectangle;
ps2->draw();
ps1->Shape::draw();//调用Shape的draw
ps2->Shape::draw();

(三)

声明impure
virtual函数的目的,是让derived classes继承该函数的接口和缺省实现。

允许impure
virtual函数同时指定函数声明和函数缺省行为,却有可能造成危险。

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 {...};
class ModelC : public Airplane {
...//未声明fly函数,但它并不希望缺省飞行
};
Airport PDX(...);
Airplane* pa = new ModelC;
...
pa->fly(PDX);  //未说出“我要的情况下就继承了该缺省行为,酿成大灾难”

这个程序试图用ModelA和ModelB的飞行方式来飞ModelC。

问题不在Airplane::fly()有缺省行为,而在于ModelC在未明白说出“我要”的情况下就继承了该缺省行为。我们可以做到“提供缺省实现给derived classes,但除非它们明确要求,否则免谈”。

解决办法(1):此间伎俩在于切断“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)代码而招来麻烦,但它比原来的设计值得依赖。

有些人反对以不同的函数分别提供接口和缺省实现,向上述的fly和defaultFly那样。

所以我们想出了下面这种解决办法:

解决办法(2):

我们可以利用“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型飞机飞至指定目的地
}

(四)

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

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

class Shape {
public:
    ...
    int objectID() const;
}; 

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

请记住:

1.  接口继承&实现继承不同。在public继承之下,derived classes总是继承base
class的接口。

2.  Pure virtual函数只具体指定接口继承。

3.  Impure virtual函数具体指定接口继承及缺省实现继承。

4.  non-virtual函数具体指定接口继承以及强制性实现继承。

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

时间: 2024-10-05 14:50:49

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:区分接口继承和实现继承

public继承的概念,由2部分构成:函数接口(function Interface)继承和函数实现(function implementation)继承.这两种继承的差异有点像函数的声明和函数的定义之间的差异. 我们在设计class时,有时希望derived class只继承函数的接口(即函数声明):有时候希望derived class继承函数接口和实现,但又覆写它们所继承的实现:又有时候希望derived class同时继承函数的接口和实现,但不覆写任何东西. 为了更好理解上述差异,用一个绘

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