避免对派生的非虚函数进行重定义
作者:网络转载 发布时间:[ 2013/1/30 9:55:39 ] 推荐标签:
你说得没错,的确“理所应当”。但这一点无法得到保证。在特殊情况下,如果mf是非虚函数并且D类中对mf进行了重定义,那么问题出现了:
class D: public B {
public:
void mf(); // 隐藏了B::mf; 参见第33条
};
pB->mf(); // 调用B::mf
pD->mf(); // 调用D::mf
此类“双面行为”的出现,究其原因,是由于诸如B::mf和D::mf这样的非虚函数是静态绑定的(参见第37条)。这也意味着:由于我们将pB声明为指向B的指针,那么通过pB所调用的所有非虚函数都将调用B类中的版本,即使pB指向一个B的派生类的对象也是如此,正如上文示例所示。
然而,对于虚函数而言,它们在编译期间采用动态绑定(再次参见第37条),因此它们不会被这个问题困扰。如果mf是虚函数,那么无论通过pB还是pD来调用mf都会是对D::mf的调用,这是因为pB和pD实际上指向同一对象,这个对象是D类型的。
如果你正在编写D类,并且你对由B类继承而来的mf函数进行了重定义,那么D类将会表现出不稳定的行为。在特定情况下,任意给定的D对象在调用mf函数时可能表现出B或D两种不同的行为,而且决定哪种行为的因素是指向mf的指针的类型,与对象本身没有任何关系。引用同指针一样会出现这种莫名其妙的行为。
但是,本文的内容仅仅是从实际角度出发做出的分析,我知道,你真正需要的是对“避免对派生的非虚函数进行重定义”这一命题的理论推导。我很乐意效劳。
第32条解释了公有继承意味着A是一个B,第34条描述了为什么在类中声明一个非虚函数是对类本身设置的“个性化壁垒”。将上述理论应用到类B、D和非虚你函数B::mf上,我们可以得到:
·对B生效的所有东西对D也生效,这是因为所有的D对象都是B对象。
·继承自B的类必须同时继承mf的接口和实现,这是因为mf是B类中的非虚函数。
现在,如果在D类中对mf进行了重定义,那么你的设计方案中出现了一个矛盾。如果D确实需要与B不同的mf实现方案,并且对于所有的B对象,无论这些对象多么个性化,它们都必须使用B实现版本的mf,于是我们可以很简单地的出以下的结论:并不是每个D都是一个B。这种情况下,D并非公有继承自B。然而,如果我们确实需要D是B的公有继承类的话,并且D确实需要与B不同的mf实现版本,那么mf对B的“个性化壁垒”作用不复存在了。这种情况下,mf应该是虚函数。后,如果每个D确实是一个B,并且mf确实对B起到了“个性化壁垒”的作用,那么D中并不会真正的重定义mf,它也不应该做出这样的尝试。
无论从哪个角度讲,我们都必须无条件地禁止对派生的非虚函数进行重定义。
如果阅读本文给你一种似曾相识的感觉,那么你一定是对阅读过的第7条还有印象,在那里,我们解释了为什么多态基类的析构函数必须为虚函数。如果你违背了第7条的思想(比如,你在多态基类中声明了一个非虚析构函数),那么你也同时违背了本条的思想。这是因为在派生类中继承到的非虚函数一定会被重定义。即使派生类中不声明任何析构函数也是如此,这是因为,对于一些特定的函数,即使你不自己生成它们,编译器也会自动为你生成它们(参见第5条)。从本质上讲,第7条只不过是本条的一个特殊情况,只是因为它十分重要,我们才把它单列出一条来。
铭记在心
·避免在派生类中重定义非虚函数。
相关推荐
更新发布
功能测试和接口测试的区别
2023/3/23 14:23:39如何写好测试用例文档
2023/3/22 16:17:39常用的选择回归测试的方式有哪些?
2022/6/14 16:14:27测试流程中需要重点把关几个过程?
2021/10/18 15:37:44性能测试的七种方法
2021/9/17 15:19:29全链路压测优化思路
2021/9/14 15:42:25性能测试流程浅谈
2021/5/28 17:25:47常见的APP性能测试指标
2021/5/8 17:01:11