Effective C++ —— 继承与面向对象设计

如果你了解C++各种特性的意义,你会发现,你对OOP的看法改变了。它不再是一项用来划分语言特性的仪典,而是可以让你通过它说出你对软件系统的想法。一旦你知道该通过它说些什么,移转至C++世界也就不再是可怕的高要求了。

条款32:确定你的public继承塑模出is-a关系

以C++进行面向对象编程,最重要的一个规则是:public inheritance(公有继承)意味is-a(是一种)的关系。

如果你令class D以public形式继承class B,你便是告诉C++编译器(以及你的代码读者)说,每一个类型为D的对象同时也是一个类型为B的对象,反之不成立。你的意思是B比D表现出更一般化得概念,而D比B表现出更特殊化的概念。你主张:“B对象可派上用场的任何地方,D对象一样可以派上用场”,因为每一个D对象都是一种(是一个)B对象。反之如果你需要一个D对象,B对象无法效劳,因为虽然每个D对象都是一个B对象,反之并不成立。

在C++领域中,任何函数如果期望获得一个类型为基类的实参(而不管是传指针或是引用),都也愿意接受一个派生类对象(而不管是传指针或是引用)。(只对public继承才成立。)

好的接口可以防止无效的代码通过编译,因此你应该宁可采取“在编译期拒绝”的设计,而不是“运行期才侦测”的设计。

请记住:

  • “public继承”意味is-a。适用于base classes身上的每一件事情一定也使用于derived classes身上,因为每一个derived classes对象也都是一个base classes对象。

条款33:避免遮掩继承而来的名称

C++的名称遮掩规则所做的唯一事情就是:遮掩名称。至于名称是否是相同或不同的类型,并不重要。即,只要名称相同就覆盖基类相应的成员,不管是类型,参数个数,都无关紧要。派生类的作用域嵌套在基类的作用域内。

C++的继承关系的遮掩名称也并不管成员函数是纯虚函数,非纯虚函数或非虚函数等。只和名称有关。

如果你真的需要用到基类的被名称遮掩的函数,可以使用using声明式,引入基类的成员函数。

请记住:

  • derived calsses内的名称会遮掩base classes内的名称。在public继承下从来没有人希望如此。
  • 为了让被遮掩的名称再见天日,可使用using声明式或转交函数(forwarding function)。

条款34:区分接口继承和实现继承

表面上直截了当的public继承概念,经过更严密的检查之后,发现它由两部分组成:函数接口继承和函数实现继承。

成员函数的接口总是会被继承。

声明一个纯虚函数的目的是为了让派生类只继承函数接口。

声明一个虚函数的目的是让派生类继承该函数的接口和缺省实现。

声明一个非虚函数的目的是为了令派生类继承函数的接口及一份强制性实现。

请记住:

  • 接口继承和实现继承不同。在public继承之下,derived classes总是继承base class的接口。
  • pure virtual函数只具体制定接口继承。
  • 简朴的(非纯)impure virtual函数具体制定接口继承及缺省实现继承。
  • non-virtual函数具体制定接口继承以及强制性实现继承。

条款35:考虑virtual函数以外的其它选择

请记住:

  • virtual函数的替代方案包括NVI手法及Strategy设计模式的多种形式。NVI手法自身是一个特殊形式的Template Method设计模式。
  • 将机能从成员函数移到class外部函数,带来的一个缺点是,非成员函数无法访问class的non-public成员。
  • tr1::function对象的行为就像一般函数指针。这样的对象可接纳“与给定之目标签名式(target signature)兼容”的所有可调用物(callable entities)。

条款36:绝不重新定义继承而来的non-virtual函数

请记住:

  • 绝对不要重新定义继承而来的non-virtual函数。

条款37:绝不重新定义继承而来的缺省参数值

对于non-virtual函数,上一条款说到,“绝不重新定义继承而来的non-virtual函数”,而对于继承一个带有缺省参数值的virtual函数,也是如此。即绝不重新定义继承而来的缺省参数值。因为:virtual函数系动态绑定(dynamically bound),而缺省参数值确实静态绑定(statically bound)。意思是你可能会在“调用一个定义于派生类内的虚函数”的同时,却使用基类为它所指定的缺省参数值。

请记住:

  • 绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数——你唯一应该覆写的东西——却是动态绑定。

条款38:通过符合塑模出has-a或“根据某物实现出”

请记住:

  • 复合(composition)的意义和public继承完全不同。
  • 在应用域(application domain),复合意味has-a(有一个)。在实现域(implementation domain),复合意味is-implemented-in-terms-of(根据某物实现出)。
文章目录
  1. 1. 条款32:确定你的public继承塑模出is-a关系
  2. 2. 条款33:避免遮掩继承而来的名称
  3. 3. 条款34:区分接口继承和实现继承
  4. 4. 条款35:考虑virtual函数以外的其它选择
  5. 5. 条款36:绝不重新定义继承而来的non-virtual函数
  6. 6. 条款37:绝不重新定义继承而来的缺省参数值