一. 内容
-
仔细思考,public 继承其实可以分成:
函数接口(function interfaces)
继承和函数实现(function implemention)
继承。这意味着 derived class 不仅可以有 base class 函数的声明,还可以有 base class 函数的实现。 -
作为 class 的设计者,有时候你会希望 derived class 只继承成员函数的接口,也就是声明;有时候你又会希望 同时继承函数的接口和实现,但又允许覆写(override)它们继承的实现;又有时候你 希望继承函数的接口和实现,但不允许覆写任何东西。
- 对于
public 继承
,成员函数的接口总是会被继承
。因为条款32曾说:public 继承是 is-a 的关系,任何可以作用于 base class 的函数也一定可以作用于 derived class。 - 对于
pure virtual 函数
,可以让 derived class只继承函数的接口
。令人意外的是,对于 pure virtual 函数,base class 可以为其提供一份定义,但唯一可以调用它的方式是:显式使用 class 名称指出。 - 对于
impure virtual 函数
,可以让 derived class同时继承函数的接口和默认实现
。但是注意,继承函数的默认行为可能带来潜在的隐患。如果 derived class 需要特定行为而忘记覆写其函数行为,这将带来很多问题。 - 对于
non-virtual 函数
,可以让 derived class继承函数的接口和强制实现
。意味着不希望 derived class 有不同的行为。
示例
class Airplane { public: virtual ~Airplane() = default; //pure-virtual 子类继承函数接口,强制覆写 virtual void ToString() const =0; //impure-virtual/virtual 子类继承函数接口和默认函数实现,可覆写 virtual void Fly(); //non-virtual 子类继承函数接口和默认函数实现,不可覆写 void DefaultFly(); }; class ModelA : public Airplane { //public 子类继承函数接口 public: void ToString() const override; void Fly() override; };
- 对于
-
当你声明成员函数时,避免以下两个问题:
- 将所有函数都声明为 non-virtual。这会使得 derived class 没有余裕空间进行特化工作。non-virtual 函数还会带来析构问题,见条款7。实际上
任何 class 如果打算使用多态性质,都会有若干 virtual 函数
。如果你关心 virtual 函数的成本,请参考 80-20 法则:一个典型的程序有80%的执行时间花费在20%的代码身上。这个法则十分重要,这意味着平均而言你的函数调用中可以有80%是virtual,而不冲击程序的大体效率。所以当你担心是否有能力负担 virtual 函数的运行成本时,先关注那举足轻重的20%代码身上。 - 另一个常见错误是将所有函数都声明为 virtual。有时候是正确的,比如 interfaces class。然而这也可能是 class 设计者缺乏坚定立场的表现,
某些函数就是不该在 derived class 中被重新实现,你就应该把它声明为 non-virtual
。
- 将所有函数都声明为 non-virtual。这会使得 derived class 没有余裕空间进行特化工作。non-virtual 函数还会带来析构问题,见条款7。实际上
二. 总结
- 接口继承和实现继承不同。在 public 继承之下,derived class 总是继承 base class 的接口。
- pure virtual 函数只具体指定接口继承。
- 简朴的(非纯)impure virtual 函数具体指定接口继承及缺省实现继承。
- non-virtual 函数具体指定接口继承以及强制性实现继承。