以1个例子来展开本条款论述内容。假定class D是class B的派生类,class B中有1个public成员函数mf:
class B{
public:
void mf();
……
};
class D: public B {……};
由1下方式调用
D x;
B* pB=&x;
pB->mf();
D* pD=&x;
pD->mf();
上面的两次调用函数mf
得到的行动相同吗?虽然mf
是个non-virtual函数,但是如果class D中有自己定义的mf
版本,那就行动真的不同。
class D: public B {
public:
void mf();//遮掩了B::mf。见条款33
……};
pB->mf();//调用B::mf
pD->mf();//调用D::mf)。
之所以行动不1致,是由于non-virtual函数是静态绑定的(statically bound,条款 37)。pB被声明为1个pointer-to-B,通过pB调用的non-virtual函数永久是B所定义的版本。但是virtual函数是动态绑定(dynamically bound,条款 37),所以virtual函数不受这个束缚,即通过指针调用,实际调用的函数是指针真正指向对象的那个函数。
如果你打算在class D中重新定义继承自class B的non-virtual函数,D对象极可能会出现行动不1致行动。更明确1点,即任何1个D对象都可能表现出B或D的行动;决定因素不在对象本身,而在于“指向该对象之指针”当初声明类型。References也会展现出和指针1样难以理解的行动。
前面已说过,public继承是is-a 关系(条款 32)。**条款**34说过,class内声明1个non-virtual函数会为该class建立1个不变性(invariant),它凌驾其特异性(specialization)。将这两个观点实施到class B和class D上和non-virtual函数B::mf上,那末
现在在D中重新定义mf
,就会有矛盾。1、如果D真的有必要重新实现mf
(不同于B的),那末is-a 关系就不成立,由于每一个D都是B不再为真;既然这样,就不应当以public情势继承。2、如果D必须以public方式继承B,且D有需求实现不同的mf
,那末久不能反应出不变性凌驾特异性;既然这样就应当声明为virtual函数。3、如果每一个D是1个B为真,且mf
真的可以反应出不变性凌驾特异性的性质,那末D久不需要重新定义mf
了。
不论上面那个观点,结论都相同:任何情况下都不应当重新定义1个基础而来的non-virtual函数。
在条款 7已知道,base class内的析构函数应当是virtual;如果你违背了条款 7,你也就违背了本条款,由于析构函数每一个class都有,即便你没有自己编写。
总结
在继承中,只能继承两种函数:virtual和non-virtual。在条款 36中我们学到,不能重新定义1个继承而来的non-virtual函数。本条款讨论的是继承virtual函数问题,再具体1点:继承1个带有缺省参数值的virtual函数。
我们应当知道,virtual函数是动态绑定(dynamically bound),缺省参数值却是静态绑定(statically bound)。
对象的静态类型(static type)是它在程序中被声明时采取的类型,例如
class Shape{
public:
enum ShapeColor{ Red, Green, Blue};
virtual void draw(ShapeColor color=Red) const=0;
……
};
class Rectangle: public Shape{
public:
virtual void draw(ShapeColor color=Green) const;//不同缺省参数值,很糟
……
};
class Circle: public Shape{
public:
virtual void draw(ShapeColor color) const;
/*客户调用上面函数时,如果使用对象调用,必须指定参数值,由于静态绑定下这个函数不从base继承缺省值。*/
/*如果使用指针或援用调用,可以不指定缺省参数值,动态绑定会从base继承缺省参数值*/
……
};
这个继承很简单。现在这样使用
Shape* ps;
Shape* pc=new Circle;
Shape* pr=new Rectangle;
这些指针类型都是pointer-to-Shape类型,都是静态类型Shape*。对象的动态类型是指“目前所指对象类型”。动态类型可以表现出1个对象将会有甚么行动。pc动态类型是Circle*,pr动态类型是Rectangle*,ps没有动态类型(它没有指向任何对象)。动态类型可以在履行进程中改变,重新赋值可以改变动态类型。
virtual函数是动态绑定的,调用哪1份函数实现的代码,取决于调用的那个对象的动态类型。
pc->draw(Shape::Red);
pr->draw(Shape::Red);
这样调用无可非议,都带有参数值。但是如果不带参数值呢
pr->draw();//调用Rectangle::draw(Shape::Red)
上面调用中,pr动态类型是Rectangle*,所以调用Rectangle的virtual函数。Rectangle::draw函数缺省值是GREEN,但是pr是静态类型Shape*,所以这个调用的缺省参数值来自Shape class,不是Rectangle class。这次调用两个函数各出了1半的力。
C++之所以使用这么奇异的运作方式,是由于效力问题。如果缺省参数值动态绑定,编译器必须有某种办法在运行期为virtual函数决定适当的参数缺省值。这比目前实行的“在编译器决定”的机制更慢且更复杂。为了履行速度和编译器实现上的简易度,C++做了这样的取舍。
我们尝试遵照这个规则,给base class和derived class提供相同参数值
class Shape{
public:
enum ShapeColor{ Red, Green, Blue};
virtual void draw(ShapeColor color=Red) const=0;
……
};
class Rectangle: public Shape{
public:
virtual void draw(ShapeColor color=Red) const;
……
};
这样问题又来了,代码重复且带着相依性(with dependencies):如果Shape内缺省参数值改变了,那末derived classes的缺省参数值也要改变,否则就会致使重复定义1个继承而来的缺省参数值。
当时如果的确需要derived classes的缺省参数值,那末就需要替换方法。条款 35列出了1些virtual函数的替换方法,例如NVI手法:
class Shape{
public:
enum ShapeColor{ Red, Green, Blue};
void draw(ShapeColor=Red) const
{
doDraw(color);
}
……
private:
virtual void doDraw(ShapeColor color) const=0;//真正在这里完成工作
};
class Rectangle: public Shape{
public:
……
private:
virtual void draw(ShapeColor color) const;
……
};
由于non-virtual函数不会被derived覆写(条款 36),这个设计很清楚的使得draw函数的color缺省参数值总是Red。
总结