EffectiveC++读书笔记——item36(不要重定义继承的非虚拟函数)
1. 重定义继承的非虚拟函数导致行为差异
假设存在类B
和从其公有继承的类D
,类B
中有一个公有成员函数mf
:
class B {
public:
void mf();
};
class D : public B {
public:
void mf();
};
当使用D
类型的对象x
,通过指向x
的B
类型指针pB
和D
类型指针pD
调用mf
函数时:
D x;
B *pB = &x;
pB->mf();
D *pD = &x;
pD->mf();
若mf
为非虚拟函数,pB->mf()
调用的是B::mf
,pD->mf()
调用的是D::mf
,这是因为非虚拟函数是静态绑定的,调用的函数取决于指针的声明类型。而虚拟函数是动态绑定的,不会出现这种情况,若mf
为虚拟函数,通过pB
和pD
调用mf
都会导致D::mf
的调用。
2. 禁止重定义的理论依据
- 公有继承意味着 “is - a” 关系:即每个
D
对象都是一个B
对象,适用于B
对象的特性也应适用于D
对象。 - 非虚拟函数设定 “超越特殊化的不变量”:从
B
继承的类必须同时继承mf
的接口和实现。
若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
。
3. 与 Item 7 的联系
Item 7 中解释了多态基类的析构函数应该是虚拟的,若在多态基类中声明非虚拟析构函数,派生类总会重定义继承的非虚拟析构函数,这其实是本文所述规则的一个特殊情况,因该情况重要所以单独成篇。
4. 总结
在 C++ 中,绝不要重定义一个通过继承得到的非虚拟函数,否则会导致对象行为不协调,且违反公有继承和非虚拟函数的设计原则。