继承与面向对象设计
OOP需要考虑的问题:
单一继承还是多重继承?
public、protected、private继承?
virtual、非virtual继承?
缺省参数值与virtual函数有什么交互影响?
继承如何影响C++的名称查找规则?
设计选项有哪些?
Class行为需要修改时virtual函数是最佳选择吗?
public继承意味着"is-a",不要带着其他意义。
virtual函数意味着"接口必须被继承",非virtual函数意味着接口和实现都必须被继承。
条款32:确定public继承塑模出"is-a"关系
每个派生类对象同时也是一个基类对象,反之不成立。基类更一般化,派生类更特殊化。可以使用基类对象的地方也可以使用派生类对象,反之不成立。
public继承时才是这种意义。private继承意义完全不同。protected继承意义仍然很不明确。
不存在完美设计适用于所有软件。最佳设计取决于系统现在和未来希望做什么事。
public继承主张能够施与于基类对象身上的所有事情,都可以施于派生类对象身上。
条款33:避免遮掩继承而来的名称
这与作用域有关,内层作用域的名称会遮掩外围作用域的名称。
名称遮掩规则:只看名称是否相同,不管类型,不管virtual与否,也不管它是什么。
派生类作用域被嵌套在基类作用域内。编译器的做法是从内到外查找各作用域直到找到为止。
public继承却又不继承那些重载函数,违反基类和派生类的"is-a"关系。
可以使用using声明式,让基类内名称在派生类作用域可见:using Base::mf;
如果继承基类并加上重载函数,又希望重新定义或覆写其中一部分,必须为被遮掩的每个名称引入一个using声明式。
private继承时,可能并不想继承基类的所有函数,using声明式派不上用场,因为using会令所有相同名称函数在派生类中都可见。我们可以利用转交函数。
派生类内的名称会遮掩基类内的名称,public继承下从来没有人希望如此。
为了让被遮掩的名称再见天日,可以使用using声明式或者转交函数(forwarding function)。
条款34:区分接口继承和实现继承
public继承下,派生类总是继承基类的接口。
纯虚函数必须被派生类重新声明,通常在抽象类中没有定义。声明一个纯虚函数的目的就是为了让派生类只继承函数接口。
也可以为纯虚函数提供定义,但是调用时需要明确指出类名称。用途有限,比如为普通虚函数提供更平常更安全的缺省实现。
声明普通虚函数的目的是让派生类继承该函数接口和缺省实现。
分割接口和默认实现。
声明非虚函数的目的是为了让派生类继承函数的接口和一份强制性实现。
非虚函数意味着并不打算在派生类中有不同行为,不变性凌驾于特异性之上。行为不可改变,派生类不该重新定义它。
条款35:考虑virtual函数以外的其他选择
考虑问题时,不妨考虑virtual函数的替代方案。
非虚接口手法(NVI),模板方法设计模式的一种特殊形式,以非虚接口包裹低访问性的virtual函数;
将virtual函数替换为函数指针成员变量;
以tr1::function成员变量替换virtual函数;
将继承体系内的virtual函数替换为另一个继承体系内的virtual函数。
条款36:绝不重新定义继承而来的非虚函数
条款37:绝不重新定义继承而来的缺省参数值
因为缺省参数值都是静态绑定,而virtual函数却是动态绑定。要用virtual函数的替代方案替代。
条款38:通过复合塑模出"has-a"或"is-implemented-in-terms-of"关系
复合有许多同义词:分层、内含、聚合、内嵌。
当复合发生在应用域内的对象之间时,表现出has-a关系;当它发生于实现域内则表现出is-implemented-in-terms-of关系。
复合的意义与public继承完全不同。
条款39:明智而审慎的使用private继承
private继承时,编译器不会自动将一个派生类对象转换为一个基类对象。
由private基类继承而来的所有成员在派生类中都变为private。
private继承意味着is-implemented-in-terms-of。派生类采用基类已备妥的某些特性。
派生类和基类不存在任何观念上的关系,private继承纯粹是一种实现技术。
private继承意味着只继承实现而略去接口。
派生类对象根据基类对象实现而得,没有其他意涵了。private继承在软件设计层面没有意义,其意义只及于软件实现层面。
is-implemented-in-terms-of尽可能使用复合,必要时才使用private继承。
复合的好处:
1、当想要阻止派生类的派生类重新定义某虚函数时,private继承无法做到。因为派生类可以重新定义虚函数,即使它们不得被调用,也就是说基类中的private虚函数,派生类也可以重新定义。
2、编译依存性降至最低。
private继承主要用于"当一个派生类想要访问基类protected成分或是为了重新定义虚函数",或者用于空间最优化。
空类:没有非static成员变量,没有virtual函数,没有虚基类,对象不用任何空间。但是技术上,C++规定独立对象都必须有非零大小。C++通常安插一个char到空对象内,但这个约束不适用于派生类对象的基类成分,因为它非独立。
继承一个空类,EBO:空白基类最优化,一般只在单一继承时可行。空间要求高时,可以考虑private继承。
条款40:明智而审慎的使用多重继承
多重继承可能会导致歧义,以及对virtual继承的需要。virtual继承是有代价的。
非必要是不使用virtual继承,平常使用非virtual继承。如果必须使用virtual基类,尽可能避免在其中放置数据。
尽量使用单一继承,但是多重继承也有正当用途,比如“public继承某个接口类,private继承某个协助实现的类”的组合。
《Effective C++ 改善程序与设计的55个具体做法》这本书就先看到这里了,后面的章节暂时还用不到,接下来要看另外的一本书了——《More Effective C++ 35个改善编程与设计的有效方法》。