读书笔记 effective c++ Item 36 永远不要重新定义继承而来的非虚函数
1. 为什么不要重新定义继承而来的非虚函数——实际论证
假设我告诉你一个类D public继承类B,在类B中定义了一个public成员函数mf。Mf的参数和返回类型并不重要,所以假设它们都是void。实现如下:
class B {
public:
void mf();
...
};
lass D: public B { ... }
我们不需要了解B,D或者mf的任何细节,考虑一个类型D的对象x,
D x; // x is an object of type D
你会感到很吃惊,如果下面的语句:
B *pB = &x; // get pointer to x pB->mf(); // call mf through pointer
同下面的语句行为不一样
D *pD = &x; // get pointer to x pD->mf(); // call mf through pointer
因为两种情况中你都触发了对象x的成员函数mf。因为两种情况使用了相同的对象和相同的函数,行为就应该是相同的,不是么?
应该是这样。但是可能也不是这样。特别的,如果mf是非虚函数并且D定义了自己的mf版本的情况下,上面的行为会不一样:
class D: public B {
public:
void mf(); // hides B::mf; see Item 33 ... };
pB->mf(); // calls B::mf pD->mf(); // calls D::mf
这种两面性行为的原因是像B::mf和D::mf这样的非虚函数都是静态绑定的(statically bound Item
37)。这意味着因为pB被声明为指向B的指针,通过pB触发的非虚函数都会是定义在类B上的函数,即使pB指向的是B的派生类对象,也就是这个例子中所实现的那样。
而虚函数是动态绑定的(dynamically bound
Item 37),所以它们不会有上面的问题。如果mf是一个虚函数,无论通过pB或者pD对mf进行调用都会触发D::mf,因为pB或者pD真正指向的是类型D的对象。
如果你实现一个类D并且重新定义继承自类B的非虚函数mf,D对象也会表现出不一致的行为。特别的,通过D对象调用mf函数的行为可能会像B也可能像D,而对象本身并不是决定因素,决定因素是指向D对象的指针类型。引用所展示出来的行为会同指针一样。
2. 为什么不要重新定义继承而来的非虚函数——理论论证
上面只是实际论证。我知道你真正想了解的是“不要重新定义继承而来的非虚函数”的理论证明。看下面的分析:
Item 32解释了pulibc继承意味着”is-a”,Item34中描述了为什么在一个类中声明一个非虚函数就是为这个类建立了一个特化上的不变性(invariant over
specialization)。如果你将这些观察应用到类B或者D以及非虚成员函数B::mf上,你会发现:
- 应用在类B上的任何事情同样能够应用在类D上,因为每个D对象都是一个B对象
- 派生自类B的类同时继承了mf的接口和实现,因为mf是B中的非虚函数。
现在,如果D重新定义了mf,你的设计就会出现矛盾。如果D真的想实现一个不同于B的mf,并且如果每个B对象——无论怎么特化——真的必须为mf使用B的实现,每个D对象都是一个B对象就不是真的了。在这种情况下,D不应该public继承自B。从另外一个方面,如果D真的必须public继承自B,并且D真的需要实现一个不同于B的mf,那么mf为B反应出来的特化上的不变性就不再为真。在这种情况下,mf应该是virtual的。最后,如果每个D真的是一个B,并且如果mf真的为B反应出了特化上的不变性,那么D真的不需要重新定义mf,也不应该尝试去这么做。
无论哪个观点,结论都相同,禁止重新定义一个继承而来的非虚函数。
3. 你应该对此条款似曾相识
如果阅读这个条款的时候给你一种似曾相识的感觉,可能是因为你已经读过Item7了,这个条款解释了为什么多态基类中的虚函数应该为virtual的。如果你违反了Item7中的条款(也就是你在多态基类中声明一个非虚析构函数),你也会违反这个条款,因为派生类总是会重新定义继承而来的非虚函数:基类的析构函数。这对于没有定义析构函数的派生类来说也为真,因为正如Item 5中所解释的,如果你自己没有声明析构函数,编译器会为你自动生成一个。从本质上来说,Item7只是这个条款的一个特例而已,尽管它足够重要到单独成为一个条款。
4. 总结
- 永远不要重新定义一个继承而来的非虚函数。
读书笔记 effective c++ Item 36 永远不要重新定义继承而来的非虚函数的更多相关文章
- 读书笔记 effective c++ Item 37 永远不要重新定义继承而来的函数默认参数值
从一开始就让我们简化这次的讨论.你有两类你能够继承的函数:虚函数和非虚函数.然而,重新定义一个非虚函数总是错误的(Item 36),所以我们可以安全的把这个条款的讨论限定在继承带默认参数值的虚函数上. ...
- 读书笔记 effective c++ Item 9 绝不要在构造函数或者析构函数中调用虚函数
关于构造函数的一个违反直觉的行为 我会以重复标题开始:你不应该在构造或者析构的过程中调用虚函数,因为这些调用的结果会和你想的不一样.如果你同时是一个java或者c#程序员,那么请着重注意这个条款,因为 ...
- 读书笔记 effective c++ Item 7 在多态基类中将析构函数声明为虚析构函数
1. 继承体系中关于对象释放遇到的问题描述 1.1 手动释放 关于时间记录有很多种方法,因此为不同的计时方法创建一个TimeKeeper基类和一些派生类就再合理不过了: class TimeKeepe ...
- 《effective C++》:条款36——绝不重新定义继承而来的非虚函数
(1)当派生类中重写了基类的非虚函数时,这个时候这个函数发生的是静态绑定 下面中的代码中: 定义一个基类B,基类定义了函数fcm,fcm是非虚的函数. 定义一个派生类D,派生类重新定义了fcm. 当用 ...
- 读书笔记 effective c++ Item 43 了解如何访问模板化基类中的名字
1. 问题的引入——派生类不会发现模板基类中的名字 假设我们需要写一个应用,使用它可以为不同的公司发送消息.消息可以以加密或者明文(未加密)的方式被发送.如果在编译阶段我们有足够的信息来确定哪个信息会 ...
- 读书笔记 effective c++ Item 35 考虑虚函数的替代者
1. 突破思维——不要将思维限定在面向对象方法上 你正在制作一个视频游戏,你正在为游戏中的人物设计一个类继承体系.你的游戏处在农耕时代,人类很容易受伤或者说健康度降低.因此你决定为其提供一个成员函数, ...
- 读书笔记 effective c++ Item 34 区分接口继承和实现继承
看上去最为简单的(public)继承的概念由两个单独部分组成:函数接口的继承和函数模板继承.这两种继承之间的区别同本书介绍部分讨论的函数声明和函数定义之间的区别完全对应. 1. 类函数的三种实现 作为 ...
- 读书笔记 effective c++ Item 39 明智而谨慎的使用private继承
1. private 继承介绍 Item 32表明C++把public继承当作”is-a”关系来对待.考虑一个继承体系,一个类Student public 继承自类Person,如果一个函数的成功调用 ...
- 读书笔记 effective C++ Item 33 避免隐藏继承而来的名字
1. 普通作用域中的隐藏 名字实际上和继承没有关系.有关系的是作用域.我们都知道像下面的代码: int x; // global variable void someFunc() { double x ...
随机推荐
- Python学习--15 日期和时间
获取当前时间 # coding: utf-8 from datetime import datetime now = datetime.now() print(now) print(now.strft ...
- (@WhiteTaken)设计模式学习——工厂方法模式
这个工厂方法模式,是简单工厂的延伸,不同点在于,将某个具体的类继续细分,将核心部分抽象成一个接口.而简单工厂,把核心写在了一个类上,不利于拓展. 举个例子,简单工厂中有苹果类,香蕉类,我们创建了一个F ...
- RejectedExecutionException 分析
当往一个固定队列ArrayBlockingQueue 不停的提交任务时,会发生什么? 请看如下代码 private static final int QUEUE_SIZE = 20; private ...
- php in_array语法
bool in_array ( mixed $needle , array $haystack [, bool $strict ] ) 返回值为直或假 var_dump(in_array( ...
- PHP中的 !== 与 !=
'==' 比较两边的值是否相等,会自动转换类型: '===' 则会严格比较类型是否相同,如果类型不相同,直接返回false. 例如:'123' === 123 // => false '!=' ...
- WP8.1开发中关于如何显示.gif格式动态格式图片方法
这几天又遇到个问题,就是如何显示动态图片,本来以为和显示静态图片一样,谁知不行,在网上一查才知道WP8.1不支持.gif格式动态图片的显示: 后来又在MSDN论坛上查找,也有人问类似的问题,后来就大概 ...
- js文字滚动效果实现
纯js实现,完整代码如下: <!doctype html> <html lang="en"> <head> <meta http-equi ...
- Linux学习之Linux目录及文件系统
以往的 Windows 一直是以存储介质为主的,主要以盘符(C 盘,D 盘...)及分区来实现文件管理,然后之下才是目录,目录就显得不是那么重要,除系统文件之外的用户文件放在任何地方任何目录也是没有多 ...
- Java多线程编程(四)—浅谈synchronized与lock
一.共享资源竞争问题 在Java语言的并发编程中,由于我们不知道线程实际上在何时运行,所以在实际多线程编程中,如果两个线程访问相同的资源,那么由于线程运行的不确定性便会在这种多线程中产生访问错误.所以 ...
- 地图定位CoreLocation框架,地理位置编码与反编码
在现代互联网时代,越来越多的应用,都用到了地图定位功能,在iOS开发中,想要加入这种功能,必须基于两个框架进行开发: 1.Map Kit:用于显示地图, 2.CoreLocation:用于显示地理位置 ...