C++的OOP有可能是单一继承或多重继承,每一个继承连接(link)可以是 public,protected 或 private,也可以是 virtual 或non-virtual。成员函数的各个选项:virtual?non-virtual?pure virtual?以及成员函数和其他语言特性的交互影响:缺省参数值与virtual函数有什么交互影响?继承如何影响的名称查找规则?设计选项有哪些?如果class的行为需要修改,virtual函数是最佳选择吗?
32. 确定你的public继承塑模出is-a关系
- Derived是Base,所以能用Base的地方,就能使用Derived替换,无论是对象、指针还是引用
- 世界上并不存在适用于所有软件的"完美设计",最佳设计取决于系统希望做什么,包括现在和未来
- is-a:适用于base身上的每一件事情也一定适用于derived身上,即Derived总是继承base的所有接口
- 有的时候可能在cpp世界中与常识相反:正方形square和rectangle之间,其实并不是is-a关系,因为rectangle内部可以随意修改长宽,但是square确对长宽修改有要求
33. 避免遮掩继承而来的名称
- 当derived中有与base同名方法,则会直接隐藏掉,无论是否参数、返回值不同,甚至也无关virtual(所以多态可能导致隐藏),只与名称相关;这么做的原因是:防止在程序库或框架中建立新的Derived时附带地从疏远的base classes继承重载函数。
- 隐藏之后就类似于没有继承这些同名函数,但是这与public继承所有接口,可借助using 来解决,如
using Base::f1; - 当使用private继承时,有时候只是需要Base中某名称函数的部分,而不是全部(故using不满足),可以借助转交函数 (inline函数),如
void f1(){Base::f1();}//这里类内部实现,隐藏inline,只暴露了Base::f1的无参版本
34. 区分接口继承和实现继承
- public继承之下,Derived总是继承base的所有接口
- pure virtual 目的是为了让Derived只继承函数接口,但是其实是可以给pure virtual函数提供一一份实现的,然后使用
类名::函数名来调用 - impure virtual目的是让derived继承函数接口以及缺省实现,但正是这缺省实现,可能让derived忘记重新定义,进而导致出错,推荐使用pure virtual,然后提供实现,再在内部调用,类似于转交函数
- non-virtual目的是为了让derived继承函数接口以及一份强制性的实现,绝不应该在derived中被重新定义
35. 考虑virtual函数以外的其他选择
- 其实本章我也不知道为啥要用其他方式来替换virtual,希望以后能顿悟吧
- 具体方法参见链接考虑virtual函数以外的其他选择
36. 绝不重新定义继承而来的non-virtual函数
- 如果重新定义了,那会导致调用该函数会因为指针的类型(base or derived)不同而不同
- 谨记non-virtual函数是为该class建立起一个不变性
37. 绝不重新定义继承而来的缺省参数值
- 由条框36可知,重新定义的只能是virtual函数,故此条款针对的virtual函数
- 缺省参数值,其实是静态类型,编译器根据指针类型,去绑定指定值,而virtual却是为了动态,两者其实天生矛盾
- 本条应该是说:永远不要在虚函数中定义默认参数
- 如果希望使用base类提供的默认值,可使用NVI(public non-virtual 调用private virtual函数,派生类重定义virtual函数即可,外部调用non-virtual函数):
cpp
class Base {
public:
void foo(int x = 10) { // 非虚,统一管理默认值
doFoo(x);
}
private:
virtual void doFoo(int x) { /* 基类实现 */ }
};
class Derived : public Base {
private:
void doFoo(int x) override { /* 派生类实现 */ }
};
38。 通过复合模塑出has-a或"根据某物实现出"
- 复合,某种类型的对象含有其他类型的对象
- 应用域,对象相对于所塑造世界中的某些事物,比如人等
- 实现域,实现细节上的人工制品,如缓冲区、互斥器、查找树等
- 当复合发生在应用域就是has-a关系(如人有地址电话),实现域就是"根据某物实现出"的关系(如根据queue实现出stack)
39. 明智而审慎地使用private继承
- 如果是private继承,编译器不会自动将derived class转换为base class对象;private继承而来的所有成员都会变为private属性
- private继承意味"由某物实现出"
- private继承意味着只有实现部分被继承,接口部分应略去
- 尽可能使用复用,只有当protected成员或virtual函数牵扯进来时,才使用private继承
- private还可能有empty base最优化,能节省内存
40. 明智而审慎地使用多重继承
- 多重继承可能引入歧义性、菱形继承的问题
- virtual继承会大小、速度等成本,且virtual base的初始化责任是由继承体系中最底层class负责,故尽量不要在virtual base中有数据
- 当涉及"public继承某个interface class"和"private继承某个协助实现的class"的两相组合,则不得不使用多重继承