《Effective C++》学习笔记——条款37


六、继承与面向对象设计


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




  • 本条款主要讨论:继承一个带有缺省参数的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;
    ...
};
class Circle : public Shape  {
public:
    virtual void draw(ShapeColor color) const;    // 注释①
    ...
};

Shape *ps;
Shape *pc = new Circle;
Shape *pr = new Rectangle;

现在,Rectangle类 与 Circle类 都继承自 Shape类。
ps、pc、pr 在声明时,是指向Shape的指针,所以,静态类型都是 Shape*
但 pc、pr 指向了两个类,所以它们动态类型分别为 Circle* 与 Rectangle* (ps未指向任何对象,所以没有动态类型)

pc->draw(ShapeColor::Red);
pr->draw(ShapeColor::Red);

分别相当于调用:

Circle::draw(ShapeColor::Red)
Rectangle::draw(ShapeColor::Red)

但,如果是

pr->draw();

理论上来讲,pr的动态类型是Rectangle* ,所以应该是 Rectangle::draw(ShapeColor::Green), 但由于 pr 的静态类型是Shape*, 它缺省参数值来自Shape class,也就是 ShapeColor::Red。

原因 与 错误的方法

C++ 这么做是为了节省执行效率,因为当 缺省参数值是动态绑定时,编译器就需要在运行期为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;
    ...
};

—— 代码重复,而且不易修改维护


替换它, NVI手法

当然,对于这种需求,也非束手无策。
既然这条路行不通,那我们换条路,用别的替代它。
在条款35中就有许多替代方法,这里讲述 NVI手法
让基类的public non-virtual函数 调用 private virtual函数,让 non-virtual函数负责指定缺省参数值,virtual函数负责实现具体的东西。

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 Rectangle : public Shape {
public:
    ...
private:
    virtual void doDraw(ShapeColor color) const;
    ...
};

因为 non-virtual函数 绝对不应该被重定义(根据条款36),所以draw函数的 color缺省值,应该永远为ShapeColor::Red

请记住
  • 绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数(唯一应该被重定义的东西)是动态绑定。





注释:

  • ①. 这样定义以后,如果用户通过对象调用此函数,一定要指定参数值。因为,静态绑定下这个函数并不会继承基类的缺省参数值。但如果通过指针(或引用)来调用这个函数,就可以不指定参数值,因为动态绑定下这个函数会继承基类的缺省参数值。




你可能感兴趣的:(学习笔记)