条款32:确定你的public继承保证了is-a关系
public继承的意思是:子类是一种特殊的父类,这就是所谓的“is-a”关系(适用于基类身上的每一件事情一定也适用于继承类身上)。但是本条款指出了其更深层次的意义:在使用public继承时,子类必须涵盖父类的所有特点,必须无条件继承父类的所有特性和接口(因为我们可以认为每一个派生类对象也都是一个基类对象)。之所以单独指出这一点,是因为如果单纯偏信生活经验,会犯错误。
比如鸵鸟是不是鸟这个问题,如果我们考虑飞行这一特性(或接口),那么鸵鸟类在继承中就绝对不能用public继承鸟类,因为鸵鸟不会飞,我们要在编译阶段消除调用飞行接口的可能性;但如果我们关心的接口是下蛋的话,按照我们的法则,鸵鸟类就可以public继承鸟类。同样的道理,面对矩形和正方形,生活经验告诉我们正方形是特殊的矩形,但这并不意味着在代码中二者可以存在public的继承关系,矩形具有长和宽两个变量,但正方形无法拥有这两个变量——没有语法层面可以保证二者永远相等,那就不要用public继承。
所以在确定是否需要public继承的时候,我们首先要搞清楚子类是否必须拥有父类每一个特性,如果不是,则无论生活经验是什么,都不能视作”is-a”的关系。public继承关系不会使父类的特性或接口在子类中退化,只会使其扩充。
条款 33:避免遮掩继承而来的名称
之前我们了解过 C++ 名称查找法则,这在继承体系中也是类似的,当我们在派生类中使用到一个名字时,编译器会优先查找派生类覆盖的作用域,如果没找到,再去查找基类的作用域,最后再查找全局作用域。名称遮盖问题——名称在作用域级别的遮盖是和参数类型以及是否虚函数无关的,即使子类重载了父类的一个同名,父类的所有同名函数在子类中都被遮盖。
考虑以下情形:
class Base {
public:
void mf();
void mf(double);
};
class Derived : public Base {
public:
void mf();
};
这样会导致派生类无法使用来自基类的重载函数,因为派生类中的名称mf
掩盖了来自基类的名称mf
。
对于名称掩盖问题的一种方法是使用using
关键字:
class Derived : public Base {
public:
using Base::mf;
};
using
关键字会将基类中所有使用到名称mf
的函数全部包含在派生类中,包括其重载版本。
若有时我们不想要一个函数的全部版本,只想要单一版本(特别是在private继承时),可以考虑使用转发函数(forwarding function):
class Base {
public:
virtual void mf();
virtual void mf(double);
};
class Derived : public Base {
public:
virtual void mf() {
Base::mf();
}
};