程序员人生 网站导航

《Effective C++》:条款36-条款37

栏目:php教程时间:2015-09-17 08:13:49

  • 条款36绝不重新定义继承而来的non-virtual函数
  • 条款37绝不要重新定义继承而来的缺省参数值

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

以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上,那末

  • 适用于B对象的每件事,也适用于D对象。(is-a 关系)
  • B的derived classes1定会继承mf的接口和实现,由于mf是1个non-virtual函数。

现在在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都有,即便你没有自己编写。

总结

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

条款37:绝不要重新定义继承而来的缺省参数值

在继承中,只能继承两种函数: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。

总结

  • 不要重新定义1个继承而来的缺省参数值,由于缺省参数值是静态绑定,而virtual函数(你唯1应当覆写的东西)是动态绑定。
------分隔线----------------------------
------分隔线----------------------------

最新技术推荐