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

被折叠的 条评论
为什么被折叠?



