一. 内容
-
考虑以下示例,一个 D class 继承自 B class 的体系中,两者均定义了同样的 non-virtual 函数:
class B { public: std::string ToString() const{ return "B"; } }; class D : public B { public: std::string ToString() const{ return "D"; } }; inline void BTry() { D Derived; B* BasePointer = &Derived; D* DerivedPointer = &Derived; std::cout << BasePointer->ToString() << "\n"; //B std::cout << DerivedPointer->ToString() << "\n"; //D }
-
结果令人奇怪,毕竟两个指针都指向对象 Derived ,并都调用相同的 ToString 函数,但结果却不一样。首先按照条款33,
Derived class 声明的同名函数 已经将来自 Base Class 中的 ToString 函数遮掩
,所以通过 Derived* 访问不了 Base::ToString 函数。但现在是指向同一对象,按理说都是处理 Derived 作用域,为什么使用 Base* 指向 Derived 时可以调用 Base::ToString 函数呢?
因为
non-virtual 函数是静态绑定的
,见条款37,也就是说通过 Base* 指针声明时,non-virtual 就已经根据它的指针类型绑定了函数,而virtual 函数可以在赋值中改变,所谓动态绑定
,意味着无论通过 Base* 或者 Derived* ,因为它们实际指向同样的 Derived 对象,所以都会调用 Derived::ToString 函数。 -
当然,以上只是表象的讨论,我知道你真正想要理论上的东西。条款32已经说过,
所谓 public 继承是 is-a 的关系
,条款34则描述为什么声明 non-virtual 函数强调了 class 的不变性,把这两点放在 class B,D 和 non-virtual 函数 B::ToString上:- 适用于 B 对象的每一件事,也适用于 D 对象,因为每一个 D 对象都是 B 对象。
- B 的 derived classes 一定会继承 ToString 的接口和实现,因为 ToString 是一个 non-virtual 函数。
现在,
如果 D 重新定义 ToString ,你的设计便出现矛盾
。如果 D 真的有必要实现出于 B 不同的 ToString,那么适用于 B 对象的每一件事便不再适用于 D。既然如此,D 便不该以 public 形式继承 B。另一方面,如果 D 真的必须以 public 继承 B,并且 D需要表现出于 B 不同的 ToString,那么 ToString 应该设计成 virtual 函数。最后,如果每一个 D 都是 B,并且不希望 D 特化 ToString,那么 D 便不应该重新定义 ToString。无论哪个观点,结论都相同:
任何情况下都不该重新定义一个继承而来的 non-virtual 函数
。 -
如果你感到枯燥乏味,或许是因为你已经读过条款7,该条款解释为什么多态性质的 base classes 应该声明 virtual 析构函数。
如果你在多态性质下的 base class 声明了 non-virtual 函数,那么derived class 便绝不应该重新定义一个继承而来的 non-virtual 析构函数
。但即使你没有定义,条款5曾说,编译器会默认为你生成它
,所以多态性质的 base classes 都需要 virtual 析构函数。因此就本质而言,条款7只不过是本条款的一个特殊案例,尽管它足够重要到单独成为一个条款。
二. 总结
- 绝对不要重新定义继承而来的 non-virtual 函数。