先看下面的例子:

 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. 关于Java中final关键字的详细介绍

    Java中的final关键字非常重要,它可以应用于类.方法以及变量.这篇文章中我将带你看看什么是final关键字?将变量,方法和类声明为final代表了什么?使用final的好处是什么?最后也有一些使 ...

  2. Unity教程之-Unity3d中针对Android Apk的签名验证(C#实现)

    当Unity开发的游戏以Android Apk的形式发布之后,经常会遇到的一种情况就是别人对我们的游戏进行二次打包,也就是用他们的签名替换掉我们的签名,从而堂而皇之的将胜利果实占为己有.面对这样的情况 ...

  3. VBA笔记-参考教程

    参考教程1: http://www.cnblogs.com/wuzhiblog/tag/VBA/ 1. VBA中字符换行 VBA中字符换行显示需要使用换行符来完成.下面是常用的换行符          ...

  4. Motan

    https://github.com/weibocom/motan/wiki/zh_userguide http://www.cnblogs.com/mantu/p/5885996.html(源码分析 ...

  5. HTML5 localStorage、sessionStorage 作用域

    一.localStorage localStorage有效期:永不失效,除非web应用主动删除. localStorage作用域:localStorage的作用域是限定在文档源级别的.文档源通过协议. ...

  6. WebApi参数问题方案

    原文:http://www.cnblogs.com/landeanfen/p/5337072.html

  7. FM的推导原理--推荐系统

    FM:解决稀疏数据下的特征组合问题  Factorization Machine(因子分解机) 美团技术团队的文章,觉得写得很好啊:https://tech.meituan.com/deep-unde ...

  8. opencv(4)实现数据增加小工具

    数据增加(data augmentation),作为一种深度学习中的常用手段,数据增加对模型的泛化性和准确性都有帮助.数据增加的具体使用方式一般有两种,一种是实时增加,比如在Caffe中加入数据扰动层 ...

  9. SQLSERVER中的人民币数字转大写的函数实现

    CREATE  FUNCTION [dbo].[f_num_chn] (@num numeric(14,5))RETURNS varchar(100) WITH ENCRYPTIONASBEGIN-- ...

  10. MySQL学习笔记:三种组内排序方法

    由于MySQ没有提供像Oracle的dense_rank()或者row_number() over(partition by)等函数,来实现组内排序,想实现这个功能,还是得自己想想办法,最终通过创建行 ...