你说得没错,的确“理所应当”。但这一点无法得到保证。在特殊情况下,如果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条只不过是本条的一个特殊情况,只是因为它十分重要,我们才把它单列出一条来。

  铭记在心

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