先看下面的例子:

 enum MyColor
{
RED,
GREEN,
BLUE,
}; class Shape
{
public:
void virtual Draw(MyColor color = RED) const = ;
}; class Rectangle: public Shape
{
public:
void Draw(MyColor color = GREEN) const
{
cout << "default color = " << color << endl;
}
}; class Triangle : public Shape
{
public:
void Draw(MyColor color = BLUE) const
{
cout << "default color = " << color << endl;
}
}; int main()
{
Shape *sr = new Rectangle();
Shape *st = new Triangle();
cout << "sr->Draw() = "; // ?
sr->Draw();
cout << "st->Draw() = "; // ?
st->Draw(); delete sr;
delete st;
}

问号所在处的输出是什么?

要回答这个问题,需要回顾一下虚函数的知识,如果父类中存在有虚函数,那么编译器便会为之生成虚表与虚指针,在程序运行时,根据虚指针的指向,来决定调用哪个虚函数,这称之与动态绑定,与之相对的是静态绑定,静态绑定在编译期就决定了。

实现动态绑定的代价是比较大的,所以编译器在函数参数这部分,并没有采用动态绑定的方式,也就是说,默认的形参是静态绑定的,它是编译期就决定下来了。

我们看下这两行代码,分析一下:

 Shape *sr = new Rectangle();
Shape *st = new Triangle();

sr的静态类型是Shape*,动态类型才是Rectangle*,类似地,st的静态类型是Shape*,动态类型是Triangle*。这里没有带参数,所以使用的是默认的形参,即为静态的Shape::Draw()里面的缺省值RED,所以两个问题所在处的输出值都是0。

正因为编译器并没有对形参采用动态绑定,所以如果对继承而来的虚函数使用不同的缺省值,将会给读者带来极大的困惑,试想一下下面两行代码:

 Shape *sr = new Rectangle(); // 默认值是RED
Rectangle *rr = new Rectangle(); // 默认值是GREEN

如果一定要为虚函数采用默认值,那么只要在父类中设定就可以了,可以借用条款35所说的NVI方法,像下面这样:

 class Shape
{
public:
void DrawShape(MyColor color = RED)
{
Draw(color);
}
private:
virtual void Draw(MyColor color) const =
{
cout << "Shape::Draw" << endl;
}
}; class Rectangle: public Shape
{
private:
void Draw(MyColor color) const
{
cout << "Rectangle::Draw" << endl;
}
}; class Triangle : public Shape
{
private:
void Draw(MyColor color) const
{
cout << "Triangle::Draw" << endl;
}
}; int main()
{
Shape *sr = new Rectangle();
Shape *st = new Triangle();
cout << "sr->DrawRectangle() = "; // Rectangle::Draw
sr->DrawShape();
cout << "st->DrawTriangle() = "; // Triangle::Draw
st->DrawShape();
delete sr;
delete st;
}

因为前面条款已经约定non-virtual函数不会被覆写,所以这样就不用担心在子类中出现定义不同缺省形参值的问题了。

最后总结一下:

绝对不要重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定,而virtual函数——你唯一应该覆写的东西——却是动态绑定。

读书笔记_Effective_C++_条款三十七:绝不重新定义继承而来的缺省参数值的更多相关文章

  1. 条款37:绝不重新定义继承而来的缺省参数值(Never redefine a function's inherited default parameter value)

    NOTE: 1.绝不重新定义一个继承而来的缺省参数值,因为缺省参数值都是静态绑定的,而virtual 函数-----你唯一应该覆盖的东西----却是动态绑定的.

  2. 读书笔记_Effective_C++_条款三十六:绝不重新定义继承而来的non-virtual函数

    这个条款的内容很简单,见下面的示例: class BaseClass { public: void NonVirtualFunction() { cout << "BaseCla ...

  3. 读书笔记_Effective_C++_条款三十九:明智而审慎地使用private继承

    private继承的意义在于“be implemented in turns of”,这个与上一条款中说的复合模型的第二层含义是相同的,这也意味着通常我们可以在这两种设计方法之间转换,但书上还是更提倡 ...

  4. 读书笔记_Effective_C++_条款三十:了解inline的里里外外

    学过基本程序课的同学都知道,inline是内联的关键字,它可以建议编译器将函数的每一个调用都用函数本体替换.这是一种以空间换时间的做法.把每一次调用都用本体替换,无疑会使代码膨胀,但可以节省函数调用的 ...

  5. 读书笔记_Effective_C++_条款三十四:区分接口继承和实现继承

    这个条款书上内容说的篇幅比较多,但其实思想并不复杂.只要能理解三句话即可,第一句话是:纯虚函数只继承接口:第二句话是:虚函数既继承接口,也提供了一份默认实现:第三句话是:普通函数既继承接口,也强制继承 ...

  6. 读书笔记_Effective_C++_条款三十二:确定你的public继承继承塑模出is-a关系

    这一条款是说的是公有继承的逻辑,如果使用继承,而且继承是公有继承的话,一定要确保子类是一种父类(is-a关系).这种逻辑可能与生活中的常理不相符,比如企鹅是生蛋的,所有企鹅是鸟类的一种,直观来看,我们 ...

  7. 读书笔记_Effective_C++_条款四十七:请使用trait classes来表示类型信息

    这一条款主要来讨论模板中迭代器的属性iterator_category,它可以通过类似于vector<int>::iterator::iterator_category的方式来取得. 到这 ...

  8. 读书笔记_Effective_C++_条款三十三:避免遮掩继承而来的名称

    名称的遮掩可以分成变量的遮掩与函数的遮掩两类,本质都是名字的查找方式导致的,当编译器要去查找一个名字时,它一旦找到一个相符的名字,就不会再往下去找了,因此遮掩本质上是优先查找哪个名字的问题. 而查找是 ...

  9. 读书笔记_Effective_C++_条款二十七:尽量少做转型动作

    有关转型的几种做法,已经在早些的博客中写过了.这里先简单回顾一下,再讲一讲effective中对之更深入的阐述. 转型可以按风格可以分成C风格转型和C++风格转型两大类,C风格转型很容易看到,因为我们 ...

随机推荐

  1. ModelState验证部分属性

    ModelState.Remove("Name") 去掉不需要验证的属性.

  2. GLOBAL_NAMES参数研究

    最近在配置Stream时,发现必须要把GLOBAL_NAMES参数的指设置为TRUE,具体原因为何不知.但是发现在设置了该参数之后,数据库每天的物化视图刷新出现了问题.之后查明原因,是DBLINK出现 ...

  3. Python基础 - eazy_install和pip源设置

    在国内一般推荐豆瓣的源,虽然工作中用到的都是公司内部的源,出于安全考虑这里就不拿公司的源举例了~ 1. pip源设置 打开~/.pip/pip.conf文件,若文件不存在则创建文件或者直接mkdir ...

  4. 如何阻止点击scrollviewer里面的单位内容时,自动滚动

    <Style TargetType="{x:Type ListBoxItem}"> <Setter Property="FocusVisualStyle ...

  5. NYOJ 石子合并(一)(区间DP)

    题目链接:http://acm.nyist.edu.cn/JudgeOnline/problem.php?pid=737 题目大意: 有N堆石子排成一排,每堆石子有一定的数量.现要将N堆石子并成为一堆 ...

  6. Winafl学习笔记

    最近在跟师傅们学习Winafl,也去搜集了一些资料,有了一些自己的理解,就此记录一下. Winafl是一个运行时插桩工具,可以提高crash的捕获率. 同时也有自己的遗传算法,可以根据代码覆盖程度进行 ...

  7. Adapter 适配器

    意图 将一个类的接口转换成客户希望的另外一个接口.Adapter模式使得原本由于接口不兼容而不能一起工作的那些类可以一起工作. 动机 在软件开发中,有的时候系统的数据和行为都正确,但接口不符合,我们应 ...

  8. Spring的配置文件ApplicationContext.xml配置头文件解析

    Spring的配置文件ApplicationContext.xml配置头文件解析 原创 2016年12月16日 14:22:43 标签: spring配置文件 5446 spring中的applica ...

  9. jenkins远程执行shell

    旧版本: 安装插件 SSH plugin 1. 增加一个domain,点击OK 点击 adding some credentials 填写要远程连接的服务器的用户名和密码(以下例子为连接到91机器的r ...

  10. 常见的mysql数据库sql语句的编写和运行结果

    省份城市试题#省份表    -> select * from province;+----+----------+| id | province |+----+----------+|  1 | ...