C++ 绝不重新定义继承而来的缺省参数值

20180326 C++ 绝不重新定义继承而来的缺省参数值



诚然在C++中,只能继承两种函数:虚(virtual)函数和非虚(non-virtual)函数。而重新定义一个继承而来的非虚(non-virtual)函数永远是错误的。所以本节主要讨论的就是“继承一个带有缺省参数值的虚(virtual)函数”。


我们知道:




/***************************************************/
/****      虚(virtual)函数是动态绑定            ****/
/****      而缺省参数值确实动态绑定             ****/
/***************************************************/


静态绑定又叫前期绑定(early binding);动态绑定又叫后期绑定(late binding)。


对象的所谓静态类型(static type),是指它在程序中被声明时所采用的类型,eg:
//一个用以描述几何形状的class
class Shape{
public:
  enum ShapeColor{Red,green,Blue};
  //所有形状都必须提供一个函数,用来描绘出自己
  virtual void draw (ShapeColor color = Red) const;
  ...
};


class Rectangle:public Shape{
public:
  virtual void draw(ShapeColor color = Green) const;
  //赋予不同的缺省参数值,这是不好的行为。
  ...
};




class Circle:public Shape{
public:
  virtual void draw(ShapeColor color) const;
  //注意:当用该方法写时,当用户以对象调用此函数,一定要指定参数值
  //因为静态绑定下,这个函数并不从其基类继承缺省参数值。
  //但若以指针(或引用)调用此函数,可以不指定参数值,
  //因为动态绑定下,这个函数会从其基类继承缺省的参数值。
  ...
};


该继承体系图例如下:


            --------- Rectangle 
            |
            |
            |
Shape <------
            |
            |
            |
            --------- Circle


考虑以下指针:
Shape* ps;       //静态类型为Shape*
Shape* pc = new Circle;  //静态类型为Shape*
Shape* pr = new Rectangle;  //静态类型为Shape*


本例中,ps、pc和pr都被声明为pointer-to-Shape类型,所以它们都以它为静态类型,注意,不论它们真正指向声明,它们的静态类型都是Shape*。


对象的动态类型(dynamic type)则是指“目前所指对象的类型”。即动态类型可以表示出一个对象将会有声明行为。以上例而言,pc的动态类型为Circle*,pr的动态类型为Rectangle*。ps没有动态类型,因为它尚未指向任何对象。


动态类型一如其名称所言,可在程序执行过程中改变(通常是经由赋值动作):
ps = pc;//ps的动态类型如今是Circle*
ps = pr;//ps的动态类型如今是Rectangle*


虚函数是动态绑定而来,即调用一个虚函数时,究竟调用哪一份函数实现代码,取决于发出调用的那个对象的动态类型:


pc->draw(Shape::Red);//调用Circle::draw(Shape::Red);
pc->draw(Shape::Red);//调用Rectangle::draw(Shape::Red);






在带有缺省参数值的虚函数中,因为虚函数是动态绑定的,而缺省参数值却是静态绑定的,意思是你可能会在“调用一个定义于派生类里的虚函数”的同时,却使用基类为它所指定的缺省参数值:
pr->draw();   //调用Rectangle::draw(Shape::Red)


此例中,pr的动态类型是 Rectangle*,所以调用的是Rectangle的虚函数,这都没啥问题。Rectangle::draw函数的缺省参数值应该是GREEN,但由于pr的静态理性是Shape*,所以这一调用的的缺省参数来自基类Shape而非Rectangle类!所以这一函数调用有着奇怪且没人预料到的组合,由Shape类和Rectangle类的draw声明式各出一半力。


上述问题不仅局限在“ps、pc和pr都是指针”的情况,即使把指针换成引用(references)问题仍然存在。重点在于draw是个虚函数,而他有个缺省值在派生类中被重新定义了。


为什么C++要这么运作呢?  就是因为运行期效率。若缺省参数值是动态绑定的,编译器就必须有某种方法在运行期为虚函数决定适当的参数缺省值。这比目前实行的“在编译期决定”的机制更慢且更复杂。


当遵守这条规则,并同时提供缺省参数值给基类和派生类的用户,会怎样呢?
class shape{
pulic:
  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内的缺省参数值改变了,所有“重复给定缺省参数值”的哪些派生类也必须改变,否则他们最终会导致“重复定义一个继承而来的缺省参数值”。这个问题该如何解决呢?
即想让虚函数表现出你所想要的行为,有一种方法是使用虚函数的替代设计,其中之一是NVI(non-virtual interface)手法:让基类里的一个公有非虚函数调用私有虚函数,后者可被派生类重新定义。在这里我们可以让非虚函数指定缺省参数,而私有虚函数负责真正的工作:
class Shape{
public:
  enum ShapeColor{Red,Green,Blue};
  void draw(ShapeColor color = Red) const  //非虚函数
  {
    doDraw(color);    //调用一个虚函数
  }
  ...
private:
  virtual void doDraw(shapeColor color) const = 0;//真正
                            //的工作在此进行
  
};


class Rectanglre:public Shape{
public:
  ...
private:
  virtual void doDraw(ShapeColor color) const;//在这里
                        // 不需要指定缺省参数值
  ...
};


由于非虚函数应该不被派生类覆盖,这个设计就使得draw函数的color缺省参数值总是为Red。


注意:绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定的,而虚函数(你唯一应该覆写的东西)却是冬天绑定的。




















































































































































































  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值