Effective C++——条款37(第6章)

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

Never redefine a function's inherited default parameter value

    先将讨论简化, 只能继承两种函数:virtual 和non-virtual 函数.然而 重新定义一个继承而来的non-virtual 函数永远是错误的(详见 条款36),所以可以安全地 将本条款的讨论局限于"继承一个带有缺省参数值的virtual函数".
    这种情况下,本条款成立的理由就非常直接而明确了:virtual 函数系动态绑定,而缺省参数值却是静态绑定.
    对象的所谓静态类型,就是它在程序中被声明时所采用的类型
,考虑以下的 class 继承体系:
// 一个用以描述几何形状的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 = Green) const;
    ...
};
public Circle : public Shape {
public:
    virtual void draw(ShapeColor color) const;
    // 注意,以上这么写,则当客户已对象调用此函数,一定要指定参数值.
    // 因为静态绑定下这个函数并不从其base继承缺省参数值
    // 但若以指针调用此函数,可以不指定参数值
    // 因为动态绑定下这个函数会从其base继承缺省参数值
    ...
};
    考虑这些指针:
Shape *ps;
Shape *pc = new Circle;
Shape *pr = new Rectangle;
    本例中ps,pc,pr都被声明为pointer-to-Shape类型,所以它们都以它为静态类型,不论它们真正指向什么,它们的静态类型都是Shape*.
    对象的所谓动态类型则是指"目前所指对象的类型".
也就是说,动态类型可以表现出一个对象将会有什么行为.以上例而言,pc的动态类型是Circle*,pr的动态类型是Rectangle*.
    动态类型如其名称所示,可在程序执行过程中改变(通常是经由赋值动作):
ps = pc;
ps = pr;
    virtual 函数系动态绑定而来,意思是调用一个 virtual 函数时,究竟调用哪一份函数实现代码, 取决于发出调用的那个对象的动态类型:
pc->draw(Shape::Red);   // 调用Circle::draw(Shape::Red)
pr->draw(Shape::Red);   // 调用Rectangle::draw(Shape::Red)
    当考虑带有缺省参数值的 virtual 函数.virtual 函数是动态绑定,而缺省参数确实静态绑定.因此可能会在"调用一个定义于derived class内的virtual函数"的同时,却使用base class 为它所指定的缺省参数值:
pr->draw();     // 调用Rectangle::draw(Shape::Red)
    此例中,pr的动态类型是Rectangle*,所以调用的是Rectangle的 virtual 函数.Rectangle::draw函数的缺省参数应该是GREEN,但由于pr的静态类型是Shape*,所以此调用的缺省参数值来自Shape class 而非Rectangle class . 结局是这个函数调用者奇怪并且几乎绝对没有人预料得到的组合,由Shape class 和Rectangle class 的draw声明式各出一半力.
    重点在于draw是个 virtual 函数,而它有个缺省参数值在derived class 中被重定义了.
    为什么C++坚持以这种乖张的方式运作呢?答案在于 运行期效率.如果缺省参数值是动态绑定,编译器就必须有某种办法在运行期为 virtual 函数决定适当参数缺省值.这比目前实行的"在编译器决定"的机制更慢且更复杂.为了程序的执行速度和编译器实现上的简易度,C++做了这样的取舍,其结果就是如今的执行效率.
    如果遵守这条规则,并且同时提供缺省参数值给base和derived class 的用户,又会发生什么事?
class Shape {
public:
    enum ShapeColor { Red, Green, Blue };
    virtual void draw(ShapeColor color = Red) const = 0;
    ...
};
class Rectangle : public Shape {
pubic:
    virtual void draw(ShapeColor color = Red) const;
    ...
};
    代码重复,更糟糕的是,代码重复又带着相依性:如果Shape内的缺省参数值改变了,所有"重复给定缺省数值"的那些derived class 也必须改变,否则它们最终会导致"重复定义一个继承而来的缺省参数值".怎么办?
    当想要 virtual 函数表现出想要的行为但遭遇麻烦,聪明的做法是考虑替代设计. 条款35列出了不少 virtual 函数的替代设计,其中之一是NVI(non-virtual interface)手法:令base class 内的一个 public non-virtual 函数调用 private virtual 函数,后者可被derived class 重新定义.这里可以让non-virtual 函数指定缺省参数,而 private virtual 函数负责真正的工作:
class Shape {
public:
    enum ShapeColor { Red, Green, Blue };
    void draw(ShapeColor color = Red) const {    // 如今它是non-virtual
        doDraw(color);                            // 调用一个virtual
    }
    ...
private:
    virtual void doDraw(ShapeColor color) const = 0;    // 真正的工作在此处完成
};
class Rectangle : public Shape {
public:
    ...
private:
    virtual void doDraw(ShapeColor color) const;
    ...
};
    由于non-virtual 函数应该绝对不被derived class 覆写(详见条款36),这个设计很清楚地使得draw函数的color缺省参数总是Red.
    注意:
    绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而 virtual 函数——唯一应该覆写的东西——却是动态绑定.

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值