洛译小筑

别来无恙,我的老友…
随笔 - 45, 文章 - 0, 评论 - 172, 引用 - 0
数据加载中……

[ECPP读书笔记 条目36] 避免对派生的非虚函数进行重定义

现在考虑以下的层次结构:B是一个基类,D是由B的公共继承类,B类中定义了一个公有成员函数mf,由于这里mf的参数和返回值不是讨论的重点,因此假设mf是无参数无返回值的函数。即:

class B {

public:

  void mf();

  ...

};

class D: public B { ... };

即使不知道BDmf的任何信息,我们声明一个D的对象x

D x;                               // x D类型的对象

 

B *pB = &x;                        // 指向x的指针

pB->mf();                          // 通过指针调用mf函数

 

D *pD = &x;                        // 指向x的指针

pD->mf();                          // 通过指针调用mf函数

在这里,如果告诉你pD->mf()pB->mf()将拥有不同的行为,你很可能会感到意外。因为两种情况都调用了x对象的成员函数mf,两次调用使用了同一函数和同一对象,mf()理所应当具有一致的行为。难道不是吗?

你说得没错,的确“理所应当”。但这一点无法得到保证。在特殊情况下,如果mf是非虚函数并且D类中对mf进行了重定义,那么问题就出现了:

class D: public B {

public:

  void mf();                       // 隐藏了B::mf; 参见条目33

  ...

};

 

pB->mf();                          // 调用B::mf

 

pD->mf();                          // 调用D::mf

此类“双面行为”的出现,究其原因,是由于诸如B::mfD::mf这样的非虚函数是静态绑定的(参见条目37)。这也就意味着:由于我们将pB声明为指向B的指针,那么通过pB所调用的所有非虚函数都将调用B类中的版本,即使pB指向一个B的派生类的对象也是如此,正如上文示例所示。

另一方面,由于虚函数是动态绑定的(再次参见条目37),因此它们不会被这个问题困扰。如果mf是虚函数,那么无论通过pB还是pD来调用mf都会是对D::mf的调用,这是因为pBpD实际上指向同一对象,这个对象是D类型的。

如果你正在编写D类,并且你对由B类继承而来的mf函数进行了重定义,那么D类将会表现出不稳定的行为。在特定情况下,任意给定的D对象在调用mf函数时可能表现出BD两种不同的行为,决定因素将是指向mf的指针的类型,与对象本身没有任何关系。引用同样会遭遇这种令人困惑的行为。

但是,上述内容仅仅是实用层面的分析,我知道,你真正需要的是对“避免对派生的非虚函数进行重定义”这一命题的理论推导。我很乐意效劳。

条目32解释了公共继承意味着“A是一个B”,条目34描述了为什么在类中声明一个非虚函数是对类本身设置的“个性化壁垒”。将上述理论应用到类BD和非虚你函数B::mf上,我们可以得到:

B生效的所有东西对D也生效,这是因为每一个D对象都是一个B对象。

继承自B的类必须同时继承mf的接口实现,这是因为mfB类中的非虚函数。

现在,如果在D中重定义了mf,那么你的设计方案中就出现了一个矛盾。如果D确实需要与B不同的mf实现方案,与此同时,如果对于所有的B对象(无论多么个性化的)确实必须使用B实现版本的mf,于是我们可以很简单地推断出:并不是每个D都是一个B。这种情况下,D并非公共继承自B。另一方面,如果D确实必须是B的公共继承类,与此同时,如果D确实需要与B不同的mf实现版本,那么mfB的“个性化壁垒”作用就不复存在了。这种情况下,mf应该是虚函数。最后,如果每个D确实是一个B,与此同时,如果mf确实B起到了“个性化壁垒”的作用,那么D中并不会真正需要重定义mf,它也不应该做出这样的尝试。

无论从哪个角度讲,我们都必须无条件地禁止对派生的非虚函数进行重定义。

如果阅读本文给你一种似曾相识的感觉,那么你一定是对阅读过的条目7还有印象,在那里,我们解释了为什么多态基类的析构函数必须为虚函数。如果你违背了条目7的思想(也就是说,你在多态基类中声明了一个非虚构函数),那么你也就同时违背了本条的思想。这是因为在派生类中继承到的非虚函数(基类的析构函数)一定会被重定义。即使派生类中不声明任何析构函数也是如此,这是因为,对于一些特定的函数,即使你不自己声明它们,编译器也会自动为你生成(参见条目5)。从本质上讲,条目7只不过是本条的一个特殊情况,只是因为它十分重要,我们才把它单列出一个条目。

时刻牢记

避免在派生类中重定义非虚函数。

posted on 2011-12-30 22:53 ★ROY★ 阅读(2242) 评论(1)  编辑 收藏 引用 所属分类: Effective C++

评论

# re: 【读书笔记】[Effective C++第3版][第36条]避免对派生的非虚函数进行重定义  回复  更多评论   

如果在D中重定义了mf(),那么派生类中的mf()会屏蔽掉基类中mf(),想调用基类的mf(),只能通过B::mf();这样调用了
2012-01-12 16:33 | mc_ran

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理