Effective C++条款36:绝不重新定义继承而来的non-virtual函数(Never redefine an inherited non-virtual function)

Effective C++条款36:绝不重新定义继承而来的non-virtual函数(Never redefine an inherited non-virtual function)


《Effective C++》是一本轻薄短小的高密度的“专家经验积累”。本系列就是对Effective C++进行通读:

第6章:继承与面向对象设计

在这里插入图片描述


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

  假设class D以public的方式继承于class B,在类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;                               
pB->mf();                            

  或者是下面这种行为:

D *pD = &x;                              
pD->mf();                   

  这两种情况都通过对象x调用成员函数mf。由于两者调用函数相同,凭借的对象也相同,所以行为也应该相同?

  确实,理应如此,但是可能也不是这样。特别的,如果mf是非虚函数并且D定义了自己的mf版本的情况下,上面的行为会不一样:

class D: public B {
    
    
public:
	void mf();         
	...                     
};
pB->mf();         
pD->mf();        

  造成这种两面性行为的原因是:B::mf和D::mf这样的非虚函数都是静态绑定的(statically bound 条款37)。这意味着因为pB被声明为指向B的指针,通过pB触发的非虚函数都会是定义在类B上的函数,即使pB指向的是B的派生类对象,也就是这个例子中所实现的那样。

  而虚函数是动态绑定的(dynamically bound 条款37),所以它们不会有上面的问题。如果mf是一个虚函数,无论通过pB或者pD对mf进行调用都会触发D::mf,因为pB或者pD真正指向的是类型D的对象。

  如果你实现一个类D并且重新定义继承自类B的非虚函数mf,D对象也会表现出不一致的行为。特别的,通过D对象调用mf函数的行为可能会像B也可能像D,而对象本身并不是决定因素,决定因素是指向D对象的指针类型。引用所展示出来的行为会同指针一样。

1、绝不重新定义继承而来的非虚函数——理论论证

  上面只是实际论证。我知道你真正想了解的是“绝不重新定义继承而来的非虚函数”的理论证明。看下面的分析:

条款32解释了,所谓pulibc继承意味着”is-a”(是一种)的关系,条款34中描述了为什么在一个类中声明一个非虚函数就是为这个类建立了一个特化上的不变性(invariant over specialization)。如果:

  • 我们在派生类中隐藏了基类的non-virtual函数,那么基类与派生类就会产生行为上的不一致,is-a关系就消失了

  • 如果想要表现出派生类与基类的不同,那么应该将函数声明为virtual(其中虚析构函数是一个例子)

  无论哪个观点,结论都相同,禁止重新定义一个继承而来的非虚函数。

2、牢记

  • 绝对不要重新定义继承而来的non-virtual函数。

总结

期待大家和我交流,留言或者私信,一起学习,一起进步!

猜你喜欢

转载自blog.csdn.net/CltCj/article/details/128638202