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

本文探讨了C++中public继承下,基类与派生类的成员函数调用差异,特别是non-virtual与virtual函数的绑定机制。指出重新定义non-virtual函数可能导致对象行为的不一致性,并强调了在面向对象设计中正确使用虚函数的重要性。

        假设我告诉你,class D系有class B以public形式派生而来,class B定义有一个public成员函数mf。由于mf的参数和返回值都不重要,所以我假设两者皆为void。换句话说我的意思是:

class B {
public:
	void mf();
	...
};
class D: public B { ... };

        虽然我们对B,D和mf一无所知,但面对一个类型为D的对象x:

D x;  // x是一个类型为D的对象

        如果以下行为:

B* pB = &x;    // 获得一个指针指向x
pB->mf();      // 经由该指针调用mf

        异于以下行为:

D* pD = &x;    // 获得一个指针指向x
pD->mf();      // 经由该指针调用mf

        你可能会相当惊讶。毕竟两者都通过对象x调用成员函数mf。由于两者所调用的函数都相同,凭借的对象也相同,所以行为也应该相同,是吗?

        是的,理应如此,但事实可能不是如此。更明确地说,如果mf是个non-virtual函数而D定义有自己的mf版本,那就不是如此:

class D: public B {
	public:
	void mf();    // 遮掩(hides)了B::mf;见条款33
	...
};
pB->mf();    // 调用B::mf
pD->mf();    // 调用D::mf

        造成此一两面行为的原因是,non-virtual函数如B::mf和D::mf都是静态绑定(statically bound,见条款37)。这意思是,由于pB被声明为一个pointer-to-B,通过pB调用的non-virtual函数永远是B所定义的版本,即使pB指向一个类型为“B派生之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的行为:决定因素不再对象自身,而在于“指向该对象之指针”当初的声明类型。References也会展现和指针一样难以理解的行径。

        条款32以及说过,所谓public继承意味is-a(是一种)的关系。条款34则描述为什么class内声明一个non-virtual函数会为该class建立起一个不变性(invariant),凌驾其特异性(specialization)。如果你将这两个观点施行于两个classesB和D以及non-virtual成员函数B::mf身上,那么:

  • 适用于B对象的每一件事,也适用于D对象,因为每个D对象都是一个B对象;
  • B的derived classes一定会继承mf的接口和实现,因为mf是B的一个non-virtual函数。

        不论哪一个观点,结论都相同:任何情况下都不该重新定义一个继承而来的non-virtual函数。

请记住

  • 绝对不要重新定义继承而来的non-virtual函数。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值