<<Effective C++>> 读书笔记6: 继承与面向对象设计

来源:互联网 发布:vb与plc以太网通讯 编辑:程序博客网 时间:2024/05/17 23:22
每一个Item都很经典,都需要去思考揣摩,我在这里将要点抽象出来,便于日后快速回忆;我只是在做文章的“搬运工”。

        Item 32 确保public继承模拟出is-a关系
1. 使用C++进行面向对象编程时唯一最重要的准则就是:public继承意味着”is-a”, 适用于基类的每一件事也适用于派生类;要让这个规则刻骨铭心。
2. 通过“编译器”(在编译时)阻止企鹅飞翔企图的设计代替只在“运行时”检测的设计。
3. 一些适用于矩形(它的宽度可以独立于他的高度而自行变化)的事情不适用于正方形(它的宽度和高度必须相等)。但是public继承断言,适用于基类对象的每一件事也适用于派生类对象;在矩形和正方形的情况下,这个断言失效,所以用public继承模拟它们的关系是完全错误的。
    我想:这里错误的原因是正方形继承了矩形的“宽度可以独立于他的高度而自行变化”,如果正方形继承矩形的“4个角都是90度”就是正确的public继承模拟。
4. is-a关系并不是能存在于两个类之间的唯一关系;另外两个常见的类关系是 "has-a" 和 "is-implemented-in-terms-of"。因为用这些其它重要关系中的一个来不正确地模拟is-a而造成的C++设计错误并不罕见,所以应该确保理解了这些关系之间的不同,并知道在 C++ 中如何才能用它们做最好的模拟。
5. 没有一个适用于所有软件的完美设计;最好的设计依赖于系统究竟期望做什么,无论现在还是未来。

        Item 33 避免覆盖“通过继承得到的名字”
1. 在一个派生类调用基类的一件东西(例如,一个成员函数,一个类型定义,或者一个数据)时,编译器能够找到我们调用的东西是因为派生类继承到声明于基类中的东西。实际的运作方法是将派生类的作用域“嵌套”在 继承作用域之中。
2. 如果使用了public继承而又没有继承基类中的重载函数(在派生类中声明同名函数而隐藏了基类中的重载函数),就违反了 Item 32 讲解的“基类和派生类之间是 is-a 关系”。
3. 派生类中的名字覆盖(隐藏)基类中的名字,在public继承中,这从来不是想要的。为了使隐藏的名字重新可见,使用 using declarations 或者 forwarding functions(转调函数)。
4. 你不希望从基类中继承所有的函数,在public继承中,这是绝不会发生的,这还是因为,它违反了public继承在基类和派生类之间is-a关系。

        Item 34 区分接口继承和实现继承
1. 纯虚拟函数的继承就是只有接口被继承。 
2. 虚拟函数的继承指接口继承加上缺省实现继承。 
3. 非虚函数的继承指接口继承加上强制实现继承。(声明一个非虚函数的目的是使派生类既继承一个函数的接口,又继承一个强制的实现。)

        Item 35 可选的虚函数的替代方法
1. 可选的虚函数的替代方法:
  1)用非虚拟接口惯用法(NVI idiom),这是用public非虚成员函数包装可访问权限较小的虚函数“模板方法”模式的一种形式。 
  2)用函数指针代替虚函数,策略(设计)模式的显而易见的形式。 
  3)用数据成员(成员函数指针)代替虚拟函数,这样就允许使用兼容于你所需要的东西的任何可调用实体。这也是策略(设计)模式的一种形式。 
  4)用另外一个继承体系中的虚拟函数代替单独一个继承体系中的虚拟函数。这是策略(设计)模式的习以为常的实现。 
  实际上上述2/3/4都是策略模式的不同实现方式。
2. 为了避免陷入面向对象设计的习惯性道路,时不时地给车轮一些有益的颠簸;有很多其它的道路(不同的实现方式),值得花一些时间去考虑它们。

        Item 36 绝不重新定义一个从基类继承的非虚函数
1. 如果你在编写 class D 而且你重定义了一个你从 class B 继承到的非虚函数nvf(),D的对象将很可能表现出不协调的行为。特别是,当nvf()被调用时,任何给定的D对象的行为既可能像B也可能像 D,而且决定因素与对象本身无关,但是和指向它的指针的声明类型有关。引用也会像指针一样表现出莫名其妙的行为。
2. 适用于基类的每一件事也适用于派生类;从B继承的类必须同时继承非虚函数nvf()的接口和实现。如果D重定义nvf(),你的设计中就有了一处矛盾,那么每一个D都是一个(is-a)B 就完全不成立。在那种情况下,D就不应该从B public继承。
3. 如果D真的必须从B public继承,而且如果D真的需要实现不同于B的nvf(),那么nvf()反映了一个B的超越特殊化的不变量(invariant over specialization)就不会成立。在那种情况下,nvf()应该是virtual的。
4. 如果每一个D真的都是一个(is-a)B,而且如果 nvf()真的相当于一个 B的超越特殊化的不变量(invariant over specialization),那么 D就不会真的需要重定义nvf()。
   [说了半天,都是为了不破坏public继承的is-a关系。]

        Item 37 绝不重定义一个“通过继承得到的默认参数值”
1. 一个对象的动态类型取决于它当前引用的对象的类型。也就是说,它的动态类型表明它有怎样的行为。
2. 虚函数动态绑定,而缺省参数值是静态绑定。
3. 为什么C++要坚持默认参数静态绑定?答案是为了运行时效率。如果默认参数值是动态绑定,编译器就必须提供一种方法在运行时确定虚拟函数的参数的默认值,这比目前在编译期确定它们的机制更慢而且更复杂。最终的决定偏向了速度和实现的简单这一边,而造成的结果就是你现在可以享受高效运行的乐趣。

        Item 38  用组合模拟”has-a” 或 “is implement in term of”
1. 组合(composition)既意味着 "has-a"(有一个),又意味着 "is-implemented-in-terms-of"(是根据……实现的)。
2. 当组合(composition)发生在应用领域的对象之间,它表达一个has-a的关系,当它发生在实现领域,它表达一个 is-implemented-in-terms-of的关系。

        Item 39  谨慎使用私有继承
1. private继承意味着 is-implemented-in-terms-of。如果类D从类B private继承,你这样做是因为你对于利用在类B中的某些特性感兴趣,而不是因为在B 和D 的对象之间有什么概念上的关系。private继承纯粹是一种实现技术,这也就是为什么你从一个基类private继承的每一件东西都在你的类中变成private的原因:它全部都是实现的细节。
2. private继承意味着只有实现应该被继承;接口应该被忽略。如果D从 B private继承,它就意味着 D对象是根据 B 对象实现的。private继承在软件设计期间没有任何意义,只在软件实现期间才有。
3. 组合(composition)和private继承两者都意味着 is-implemented-in-terms-of,但是组合(composition)更易于理解,所以你应该尽你所能使用它。组合更好的体现了“松耦合”。
4. 与组合(composition)不同,private继承能使空基优化( empty base optimization)有效。这对于致力于最小化对象大小( object sizes)的库开发者来说可能是很重要的。
   [这篇Blog总结的不错: http://blog.csdn.net/tonywearme/article/details/7039963]

        Item 40 谨慎使用多继承
1. 除非必需,否则不要使用虚继承(解决菱形继承问题)。缺省情况下,使用非虚拟继承;如果你必须使用虚继承,试着避免在基类中放置数据。这样你就不必在意它的初始化、清空、赋值规则中的一些怪癖。
2. 多继承是面向对象工具箱里的一种工具而已,典型情况下,它的使用和理解更加复杂,所以如果你得到一个或多或少等同于一个 MI 设计的 SI 设计,则 SI 设计总是更加可取。如果你能拿出来的仅有的设计包含 MI,你应该更加用心地考虑一下——总会有一些方法使得 SI 也能做到。但同时,MI 有时是最清晰的,最易于维护的,最合理的完成工作的方法。在这种情况下,毫不畏惧地使用它。只是要确保谨慎地使用它。
3. 多继承合理的用途: 从一个接口基类public继承和从一个有助于实现的基类private继承。










0 0
原创粉丝点击