Effective C++——条款36(第6章)

来源:互联网 发布:北京java培训学校 编辑:程序博客网 时间:2024/06/06 03:22

条款36: 绝不重新定义继承而来的non-virtual 函数

Never redefine an inherited non-virtual function

    假设 class D系由 class B以 public 形式派生出来,class B定义有一个 public 成员函数mf.由于mf的参数和返回值都不重要,所以假设两者皆为 void .
class B {public:    void mf();};class B : public B { ... };
    虽然对B,D和mf一无所知,但面对一个类型为D的对象 x:
D x;        // x是一个类型为D的对象
    如果以下行为:
B *pB = &x; // 获得一个指针指向xpB->mf();   // 经由该指针调用mf
    异于以下行为:
D *pD = &x;pD->mf();
    两者都通过对象 x 调用成员函数 mf,由于两者所调用的函数都相同,所以行为应该相同,是吗?
    是的,一般如此.更明确地说,如果mf是个non-virtual 函数而D定义有自己的mf,那就不是如此:
class D : public B {public:    void mf();      // 遮掩了B::mf    ...};pB->mf();           // 调用B::mfpD->mf();           // 调用D::mf
    造成此一两面行为的原因是,non-virtual 函数如B::mf和D::mf都是静态绑定(staticlly bound,详见条款37).这意思是说,由于pB被声明为一个pointer-to-B,通过pB调用的non-virtual 函数永远是B所定义的版本,即使pB指向一个类型为"D派生的class"的对象.
    但另一方面,virtual 函数却是动态绑定(dynamically bound,详见条款37),所以它们不受这个问题困扰.如果mf是个 virtual 函数,不论是通过pB或pD调用mf,都会导致调用D::mf,因为pB和pD真正指的都是一个类型为D的对象.
    如果正在编写 class D并重新定义继承自 class B的non-virtual 函数mf,D对象很可能展现出不一致的行为.更明确地说,当mf被调用,任何一个D对象都可能展现出B或D的行为:决定因素不在对象自身,而在于"指向该对象的指针"当初的声明类型.
    但那只是实务面上的讨论,真正的理论层面的理由,接下来讨论.
    条款32已经说过,所谓 public 继承意味is-a的关系.条款34则描述为什么在 class 内声明一个non-virtual 函数会为该 class 建立起一个不变性,凌驾其特异性.如果将这两个观点施行于两个 class B和D以及non-virtual 成员函数B::mf身上,那么:
    适用于B对象的每一件事,也适用于D对象,因为每个D对象都是一个B对象.
    B的derived class 一定会继承mf的接口和实现,因为mf是B的一个non-virtual 函数.
    现在,如果D重新定义mf,设计便出现矛盾.既然D以 public 形式继承B,并且mf是B的一个non-virtual 函数,那么D的mf行为和B的mf行为必须是一致的.但D又重新定义mf,这就发生了矛盾.
    因此,任何情况下都不应该重新定义一个继承而来的non-virtual 函数.
    同时这个条款也解释了为什么多态性base class 内的析构函数应该是 virtual.如果违反了这个准则,在base class 内声明一个non-virtual 析构函数,那么在derived class 就不能重新定义一个派生类的non-virtual 析构函数.但即使没有重新定义non-virtual 析构函数,编译器也会为derived class 定义一个默认的析构函数,仍然发生矛盾.
    注意:
    绝对不要重新定义继承而来的non-virtual 函数.

0 0
原创粉丝点击