条款34:区分接口继承和实现继承
1、public
继承细分
实际上细分为:函数接口继承和函数实现继承。这两种细分更像是函数声明和函数定义之间的差异。
从这两个角度出发,public
继承可以分为:
- 只继承接口
- 同时继承接口和实现,且继承而来的实现能够被覆写
- 同时继承接口和实现,且继承而来的实现不能够被覆写
2、三种继承对应的成员函数的写法
(1)只继承接口
- 对应的成员函数的写法为:
pure virtual
。 - 特点:此类继承派生类必须自己写实现。
注意:pure virtual
函数是可以写定义的,但是只能通过类名调用。
(2)同时继承接口和实现,且继承而来的实现能够被覆写
- 对应的成员函数的写法:
virtual
。 - 特点:此类继承派生类可以缺省实现,缺省的话,就会继承基类的实现。也可以自己手动写实现,这样相当于是覆盖了基类的实现。
(3)同时继承接口和实现,且继承而来的实现不能够被覆写
对应的成员函数的写法:non-virtual
。
特点:此类继承,培生累必须继承缺省和实现,且不饿能够修改。
3、继承缺省实现的优缺点
- 继承缺省实现的好处是:提高了代码的重用性(即:相同性质的实现放在一个缺省实现中。)
- 继承缺省实现的缺点是:容易使逻辑错误,即本该重写(特殊性质)实现的派生类,没有重写,而是继承了缺省实现。
4、第二种继承存在的问题
- 缺省的实现,如果是所有派生类的共同性质,那么这样的继承缺省实现的派生类没有问题。
- 但是,如果派生类的实现应该是与缺省实现不同的,但是却继承了缺省实现的话,这样就会引发编程逻辑错误。
5、如何解决上述问题?
解决问题的思路是:使得virtual
函数的接口和实现分离,但是有两种不同的手法。
(1)方案一:
- 首先,将原本的
impure virtual
函数改写成:pure virtual
- 将原本共同的性质抽取出来,写成一个函数,放在作用域:
protected
中 - 派生自此基类的类中,如果拥有该性质,可以在实现中直接调用抽取的函数。
方案一的缺点:过度雷同的函数名称可能引起class
命名空间污染问题。
(2)方案二:
- 首先,将原本的
impure virtual
函数改写成:pure virtual
pure virtual
函数可以写定义的。将原本共同的性质抽取出来,写到该pure virtual
函数的定义中。- 派生自此基类的类中,如果拥有该性质,可以在实现中通过类名调用该
pure virtual
函数的定义。
6、接口与实现的继承写法容易犯的错误
(1)错误一:将所有函数声明为non-virtual
- 将一个不作为基类使用的类的成员函数全部声明为
non-virtual
是合理的。 - 一般打算用作基类的类,都是会有virtual函数的。
注意:关于virtual
函数成本的考虑。参考,80-20法则。即:将将重心放在举足轻重的20%的代码上,这部分代码应该重点关注效率、成本问题。
(2)错误二:将所有函数声明为virtual
- 对于接口类(
interface classes
),这样左是可以的。 - 但是一个类如果并不打算作为接口类使用,某些函数应该明确写成non-virtual 函数,即不允许派生类修改(适用于:不变性为重心,特异性无关紧要的函数)。
总之,virtual
函数主要考虑的是特异性,即派生类可能和基类拥有不同的性质的时候应该写成virtual
。non-virtual
则主要考虑的是不变形,即所有的派生类都应该拥有这一性质的话,就应该写成non-virtual
.