条款32:确定你的public继承塑模出is-a关系
以C++进行面向对象编程,最重要的一个规则是:public inheritance(公有继承)意味is-a(是一种)的关系。
在C++领域中,任何函数如果期望获得一个类型为基类的实参(而不管是传指针或是引用),都也愿意接受一个派生类对象(而不管是传指针或是引用)。(只对public继承才成立。)好的接口可以防止无效的代码通过编译,因此你应该宁可采取“在编译期拒绝”的设计,而不是“运行期才侦测”的设计。is a并不是唯一存在classes之间的关系。另两个常见的关系是has-a(有一个)和is-implemented-in-term-of(根据某物实现出)。
请记住:
“public继承”意味is-a。适用于base class身上的每一件事情一定也适用于derived class身上,因为每一个derived class对象也都是一个base class对象。
条款33:避免遮掩继承而来的名称
在继承体系下,派生类的作用域包含在基类作用域下,因而在派生类中的同名变量会遮掩基类的变量。
class Base{ private: int x; public: virtual void mf1() = 0; virtual void mf1(int); virtual void mf2(); void mf3(); void mf3(double); ... }; class Derived : public Base{ public: virtual void mf1(); void mf3(); void mf4(); ... }; Derived d; intx; ... d.mf1(); //ok,调用Derived::mf1() d.mf1(x); //error d.mf2(); //ok,调用Base::mf2 d.mf3(); //ok,调用Derived::mf3 d.mf3(x); //error
在基类中所有名为mf1和mf3的函数都被派生类中的同名函数遮掩掉了,即使它们有不同的参数类型,即使它们是虚函数或非虚函数。
由于上面是public继承,为了能够使用基类中的同名函数(也就是保持is-a关系),可以采用两种办法:使用using声明式或转交函数。
1.在上面的例子中,派生类可做以下修改: class Derived : public Base{ public: using Base::mf1; //让Base class内名为mf1和mf3的所有东西中 using Base::mf3; //在Derived作用域中可见 virtual void mf1(); void mf3(); void mf4(); ... }; Derived d; intx; ... d.mf1(); //ok,调用Derived::mf1() d.mf1(x); //OK,调用Base::mf1(int) d.mf2(); //ok,调用Base::mf2() d.mf3(); //ok,调用Derived::mf3() d.mf3(x); //ok,调用Base::mf3(int) 这就意味着如果继承基类并想重载基类函数,而你又希望重新定义或覆写其中一部分,那么为那些原本会被遮掩的每个名称引入以一个using声明式。 2.如果试图选择性地只继承部分重载函数,这在public继承下不可能发生,因为它违反了public继承所暗示的is-a关系,然而在private继承下可能有意义。例如上式Derived以private继承base,而Derived唯一想继承的mf1是那个无参数版本。using声明式这里没用,因为他声明的某给定名称的所有同名函数都 在Derived中可见,可以使用转交函数(forwarding function): class Derived : private Base{ //私有继承,is-implemented-in-term-of关系 public: virtual void mf1(){ //转交函数 Base::mf1(); } ... }; Derived d; intx; ... d.mf1(); //ok,调用Derived::mf1() d.mf1(x); //error,Base::mf1被屏蔽
请记住:
derived calsses内的名称会遮掩base classes内的名称。在public继承下从来没有人希望如此。
为了让被遮掩的名称再见天日,可使用using声明式或转交函数(forwarding function)。
条款34:区分接口继承和实现继承
表面上直截了当的public继承概念,经过更严密的检查之后,发现它由两部分组成:函数接口继承和函数实现继承。成员函数的接口总是会被继承。
pure virtual函数有两个最突出的特性:它们必须被任何“继承了它们”的具象class重新声明,而且它们在抽象class中通常没有定义。所以:声明一个pure virtual函数的目的是为了让derived class只继承函数接口。
令人意外的是,我们竟然可以为pure virtual函数提供定义。但调用它的唯一途径是“调用时明确指出其class名称”:
声明简朴的(非纯)impure virtual函数的目的,是让derived class继承该函数的接口和缺省实现。
声明non-virtual函数的目的是为了令derived class继承函数的接口及一份强制性实现。
如果成员函数是个non-virtual函数,意味着它并不打算在derived classes中有不同的行为。
请记住:
接口继承和实现继承不同。在public继承之下,derived classes总是继承base class的接口。
pure virtual函数只具体制定接口继承。
简朴的(非纯)impure virtual函数具体制定接口继承及缺省实现继承。
non-virtual函数具体制定接口继承以及强制性实现继承。
条款35:考虑virtual函数以外的其它选择
条款36:绝不重新定义继承而来的non-virtual函数
请记住:
绝对不要重新定义继承而来的non-virtual函数。
条款37:绝不重新定义继承而来的缺省参数值
请记住:
绝对不要重新定义继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数——你唯一应该覆写的东西——却是动态绑定。
条款39:明智而审慎地使用private继承
请记住:
1.private继承意味is-implementation-in-terms of(根据某物实现出)。她通常比复合级别低。但是当derived class需要访问protected base class的成员,或需要重新定义继承而来的virtual函数时,这么设计是合理的。
2.和复合不同,private继承可以造成empty base最优化。这对致力于“对象尺寸最小化”的程序库开发者而言,可能很重要。
条款40:明智而审慎地使用多重继承
请记住:
1.多重继承比单一继承复杂。它可能导致新的歧义性,以及对virtual继承的需求。
2.virtual继承会增加大小、速度、初始化(及赋值)复杂度等等成本。如果virtual base class不带任何数据,将是最具实用价值的情况。
3.多重继承的确有正当用途。其中一个情节涉及“public继承某个Interface class”和“private继承某个协助实现的class”的两相组合。