Item 36——区分接口继承和实现继承

时间:2022-03-11 10:06:17

(公有)继承的概念看起来很简单,进一步分析,会发现它由两个可分的部分组成:函数接口的继承和函数实现的继承。这两种继承类型的区别和函数声明和函数定义间的区别是完全一致的。

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

问题思考:为虚函数同时提供函数声明与默认实现是很危险的一件事!
1.当派生类类继承超类后,如果派生类忘记重新定义实现超类的虚函数,那么派生类依然可以使用超类的虚函数。但往往会出现错误的行为。
2.解决方案是:窍门在于切断虚函数的接口和它的默认实现之间的联系
将虚函数用纯虚函数替代,同时提供纯虚函数的一个实现。这样超类的声明说明了它的接口(派生类必须使用),而它的定义说明了它的默认行为(派生类可能会使用,但要明确地请求)

理解了纯虚函数、简单虚函数和非虚函数在声明上的区别,就可以精确地指定你想让派生类继承什么:仅仅是接口,还是接口和一个缺省实现?或者,接口和一个强制实现?因为这些不同类型的声明指的是根本不同的事,所以在声明成员函数时一定要从中慎重选择。只有这样做,才可以避免没经验的程序员常犯的两个错误。
第一个错误是把所有的函数都声明为非虚函数,这就使得派生类没有特殊化的余地。
第二个错误是将所有的函数都声明为虚函数。这样做往往表现了类的设计者缺乏表明坚定立场的勇气。一些函数不能在派生类中重定义,只要是这种情况,就要旗帜鲜明地将它声明为非虚函数。