《Effective C++》学习笔记(条款37:绝不重新定义继承而来的默认参数值)

最近开始看《Effective C++》,为了方便以后回顾,特意做了笔记。若本人对书中的知识点理解有误的话,望请指正!!!

我们可以将本条款的讨论局限于“继承一个带有默认参数的 虚函数”,而本条款成立的理由非常简单:虚函数 是动态绑定(又名后期绑定),而默认参数值却是静态绑定(又名前期绑定)。

对象的所谓静态类型,就是它在程序中被声明使所采用的类型。看下面的 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;
    ...
};
class Circle : public Shape  {
public:
    //没有默认参数值,调用该函数一定要赋予参数,因为静态绑定下,这个函数并不从其基类继承默认参数值
    //但若以指针或引用调用此函数,可以不指定参数值,因为动态绑定下,这个函数会从其基类继承默认参数值
    virtual void draw(ShapeColor color) const;
    ...
};
//静态类型都为 Shape* 。注意,不论它们真正指向什么,它们的静态类型都是 Shape*
Shape* ps;
Shape* pc = new Circle;
Shape* pr = new Rectangle;

对象的所谓动态类型,就是目前所指对象的类型。也就是说,动态类型可以表现出一个对象将会有什么行为。上述例子中,pc、pr 的动态类型分别是 Circle*Rectangle* ,而 ps 没有动态类型,因为它没有指向任何对象。

动态类型如其名所示,可在程序执行过程中改变(通常是经由赋值动作)

ps = pc;	//ps的动态类型如今是 Circle*
ps = pr;	//ps的动态类型如今是 Rectangle*

虚函数 是动态绑定的,所以调用一个 虚函数 时,究竟调用哪个函数取决于发出调用的那个对象的 动态类型:

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

这些 虚函数 的性质我们都有所了解。但是当遇到带有默认参数值的 虚函数 时,会怎样呢?因为 虚函数 是动态绑定,而默认参数值是静态绑定,则在你调用一个定义于 派生类 的 虚函数时,使用的却是 基类 中指定的默认参数值:

pr->draw();				//调用 Rectangle::draw(Shape::Red)!但在Rectangle定义的draw的默认参数是Shape::GREEN

Rectangle::draw 函数的默认参数值应该是 GREEN,但由于 pr 的静态类型是 Shape* ,所以此一调用的默认参数值来自于基类 Shape 中 draw 的默认参数 Shape::Red

以上情况不只局限于 “ps,pc 和 pr 都是指针” 的情况,即使把指针换成引用,问题仍然存在。重点在于 draw 是个 虚函数 ,而它有个默认参数值在 派生类 中被重新定义了。

为什么 C++ 坚持默认参数值是静态绑定呢?答案在于运行期效率。因为当默认参数值是动态绑定时,编译器就需要在运行期为 虚函数 决定适当的参数默认值,这无疑会复杂且低效。

如果我们提供默认的参数值给 派生类 及 基类,又会怎样呢?

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;
    ...
};

代码重复了。更糟糕的是,代码重复又带着相依性:如果 Shape 内的默认参数值改变了,则“重复规定默认参数值”的那些 派生类 也必须改变,否则它们最终会导致“重复定义一个继承而来的默认参数值”。怎么办呢?

当你想用 虚函数 表现出你期望的行为却遭遇麻烦,聪明的做法是考虑替代设计(见条款35)。例如使用 NVI 手法,令基类内的一个 public 普通函数调用 private 虚函数,后者可被 派生类 重新定义。这里我们让 普通函数 指定默认参数,private 虚函数 负责真正的工作:

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;
    ...
};

因为条款36中说过,普通函数绝对不被派生类重写,所以 draw 函数的 color 默认值总是 Red。

条款38:通过组合构造出has-a 或 “根据某物实现出”

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值