Effective C++条款37:继承与面向对象之(绝不重新定义继承而来的缺省参数值)

前言

  • 在条款36介绍过,在继承体系中,派生类最好只重写覆盖virtual函数,而不要去隐藏基类的non-virtual函数
  • 因此,本条款要介绍的“不要重新定义继承而来的缺省参数值”,是针对于virtual函数而言的
  • 一个重要的概念:virtual函数是动态绑定的,而virtual函数的缺省参数值却是静态绑定的

一、静态类型、动态类型

  • 静态类型:在被声明时所采用的的类型
  • 动态类型:目前所知对象的类型

演示案例

  • 下面是一个继承体系
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 = 0;
};

class Circle :public Shape {
public:
    virtual void draw(ShapeColor color)const = 0;
};

  • 现在我们定义下面的代码,它们都被声明为pinter-to-Shpae类型,因此它们不论它们指向什么,静态类型都是Shape*:
Shape* ps;                 //静态类型为Shape*
Shape* pc = new Circle;    //静态类型为Shape*
Shape* pr = new Rectangle; //静态类型为Shape*
  • 动态类型是指该该对象将会有什么行为。例如:
Shape* ps;
Shape* pc = new Circle;
Shape* pr = new Rectangle;

ps = pc; //ps的动态类型如今是Circle*
ps = pr; //ps的动态类型如今是Rectangle*
  • 根据语法我们知道,对于virtual函数的调用,是根据其动态类型决定的。例如:
Shape* ps;                 
Shape* pc = new Circle; 
Shape* pr = new Rectangle;

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

二、virtual函数的缺省参数值是静态绑定的

  • 虽然对于virtual函数的调用时动态绑定的,但是对于virtual函数的缺省参数值却是静态绑定的
  • 见下面的代码:
    • 我们知道virtual函数是动态绑定的,pr的动态类型为Rectangle,所以调用的是Rectangle::draw()
    • 但是virtual函数的缺省参数值是静态绑定的,在上面类的定义中Rectangle的draw()函数无参数,但是由于pr指针的静态类型是Shape,因此其draw()函数的缺省参数值就是Shape::draw()函数中的参数值,为Shape::Red
Shape* pr = new Rectangle;
pr->draw(); //调用的是Rectangle::draw(Shape::Red)


//Circle也是相同的道理
Shape* pc = new Circle;
pc->draw(); //调用的是Circle::draw(Shape::Red),而不是Circle::draw(Shape::Green)
  • 为什么要设计这种行为:在于运行效率。如果缺省参数值也是动态绑定,编译器就必须有某种办法在运行期为virtual函数决定适当的参数缺省值,这比目前实行的“在编译期决定”的机制更慢而且更复杂

三、不要重新定义继承而来的缺省参数值

  • 通过二,我们知道virtual函数的缺省参数值是静态绑定的。因此,我们不要重新定义继承而来的缺省参数值,因为这会在调用virtual函数时产生意想不到的效果(上面代码中,通过pc调用draw()就是一个例子)

四、针对于virtual函数的缺省参数值,给出的建议

先看一个效率低下的方案

  • 为了保持基类与派生类中的一致性,一种低效率的方法是将基类和派生类中的virtual函数的缺省参数值设置为一致的
  • 例如:
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;
};

class Circle :public Shape {
public:
    virtual void draw(ShapeColor color = Red)const;
};
  • 低效率的原因:
    • ①代码重复
    • ②依赖性太高,如果基类中的缺省参数值改变了,那么需要将派生类中的缺省参数值都修改一遍

以NVI手法定义class

  • 条款36介绍了NVI手法,对于virutal函数的缺省参数值,为了避免基类与派生类中的缺省参数值不一致,我们可以采取这种方法
  • 定义的代码如下:
class Shape {
public:
    enum ShapeColor { Red, Green, Blue };
    void draw(ShapeColor color = Red)const { //因为是non-virtual函数,因此不建议派生类隐藏
        doDraw(Red);
    }
private:
    //真正的工作在此处完成,派生类可以重写
    virtual void doDraw(ShapeColor color)const = 0;
};


class Rectangle :public Shape {
private:
    virtual void doDraw(ShapeColor color)const = 0;
};
  • 上面的doDraw()函数完成真正的功能,并且接受一个ShapeColor类型
  • 我们又定义了一个non-virtual函数draw,其参数默认为Red,并且non-virtual函数不建议派生类隐藏,因此不论是基类还是派生类调用draw()函数,参数的默认值将永远是Red,达到了我们最终的目的

五、总结

  • 绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virutal函数——你唯一应该覆盖的东西——却是动态绑定
发布了1525 篇原创文章 · 获赞 1085 · 访问量 45万+

猜你喜欢

转载自blog.csdn.net/qq_41453285/article/details/104818312