现在的位置: 首页 > 综合 > 正文

Solmyr 的小品文系列之二:模棱两可的陷阱

2013年08月22日 ⁄ 综合 ⁄ 共 3827字 ⁄ 字号 评论关闭

“为什么会这样?!”,zero 一边喝水一边嘟囔着,恨恨的看着面前显示器上的代码,“为什么这么简单的一个调用也会出现编译错误 …… ”

“这是因为你的设计太差!”

噗!zero 被幽灵一样出现在背后的 Solmyr 吓了一大跳,一口水差点全喷出来。

“咳!咳咳!S …… Solmyr ,你什么时候站在我背后的?”,zero 很费力的平息了咳嗽,同时努力回想刚才自己有没有把柄会被 Solmyr 抓到。

Solmyr 抓过一张椅子坐了下来:“在你一开始干傻事的时候我就在了,正是这个糟糕的设计导致了现在困扰你的编译错误。”

“哪 …… 哪里?”

“这儿。” Solmyr 抓过键盘,标出了下面这段代码:

void SomeFunc(int i)
…………

void SomeFunc(float f)
…………

int main()
{
…………
SomeFunc(1.2);// Error! ambiguous call
…………
}

“我也正觉得奇怪”,zero 一如既往的挠着头,试图压榨不存在的智慧,“这么简单的一个函数重载,应该很清楚才对。我这里调用时明明给出的是浮点数,显然应该调用 float 版本的 SomeFunc 。最奇怪的是如果没有这个调用,整个程序编译连接完全没有问题,可见这样重载函数是合法的。”

“嗯,没错,确实是合法的,但是合法不代表正确。zero ,你念一下这一段,看看先知 Meyers 在他的《50 诫》(注:指《Effective C++ 2/e》一书)中的条款 26 中是怎样描述 C++ 对待‘模棱两可’的哲学的。”,Solmyr 翻开了一本书,指着其中的几行。

“C++ ……”

“站起来,大声念!”

zero 依言站起,中气十足的念道:“C++ 也有一个哲学信仰:它相信潜在的模棱两可的状态不是一种错误。”

旁边的座位上传来低低的窃笑声,更远处的人探头张望,投来好奇的目光,zero 顿时感到自己像个傻瓜。当 zero 看到 Solmyr 嘴边招牌式的坏笑时明白了过来:自己又一次被 Solmyr 设计了。

“嗯,明白了这一点,我们就可以展开进一步的讨论了”,Solmyr 开始转入正题,“还记得上次我说过上面的 1.2 是什么吗?”

zero 露出了回忆的表情:“嗯 …… 1.2 是‘写在代码里的常量’…… 应该是一个 double 类型常量。”

“这就是问题所在:编译器看到这个调用函数的请求,会去寻找你的重载函数中哪个函数能够匹配这个调用请求给出的参数,结果它发现没有一个函数的参数是 double 类型的,所以必须要做类型转换,但是 double 类型既可以转成 int ,也可以转成 float ,究竟转哪个好呢?编译器不知道,所以只好报错了。明白了吗?”

zero 似懂非懂的点了点头。

“那我问你,这样重载编译时会不会报错?”,Solmyr 稍稍改动了一下 zero 的代码:

void SomeFunc(int i)
…………

void SomeFunc(double db)
…………

int main()
{
…………
float f = 1.2;
SomeFunc(f);
…………
}

zero 看了看,学着 Solmyr 的语气说到:“编译器发现没有一个函数的参数是 float 类型的,所以必须要做类型转换,但是 float 类型既可以转成 int ,也可以转成 double ,究竟转哪个好呢?编译器不知道,所以只好报错了。”

“错!”,Solmyr 顺手按下了运行按钮,程序运行一切正常,输出显示调用的是 double 版本的 SomeFunc 函数。

zero 再度感到了困惑:“为什么同样是要选择类型转换,这个就没错,前一个就有错呢?这中间的逻辑何在?”

“重要的是这一句:‘究竟转哪个好呢?编译器不知道’。你没有注意到我说这句话的时候‘好’字上用了一个重音吗?”

“你用过重音吗?”

“ …… 这个不是重点。重点在于,float 到 int 和 float 到 double 这两个转换,编译器是能够选择的,因为 float 到 int 会损失数据 —— 象样的编译器会在做这种类型转换的时候给出一个 warning —— 而 float 到 double 则不损失数据,所以编译器知道‘转哪个好’。而之前的情况,double 到 int 到 float 的转换都要损失数据,所以编译器不知道‘转哪个好’,它没办法做一个决定 —— ”,Solmyr 看了看 zero ,再度问道,“明白了吗?”

zero 皱着眉头,挠头挠的更起劲了,显然对于消化一下子出现的这么多信息感到少许困难:“我想我明白了,关键是编译器能否区分两个类型转换。在这里区分的关键是类型转换是否损失数据,嗯 …… 所以我只要在所有用到浮点数的场合都使用 double 类型,就不会有问题,即使别人用 float 来调用也一样。”

“正确。不过‘模棱两可’的问题可不仅仅出现浮点数身上,例如,这样两个重载函数 …… ”,Solmyr 接着键入:

void SomeFunc(double db)
void SomeFunc(char ch)

“如果我用一个整形变量来调用,会出现什么事情?”,Solmyr 扭头盯着 zero。

“呃 …… 编译器同样无法区分 int 到 double 和 int 到 char 这两个类型转换,所以同样会报错。”

“正确。你能够自己举出几个例子吗?”,Solmyr 把键盘递了回去。

很明显的,zero 陷入了沉思,过了一会儿,屏幕上出现了这样几行代码:

// 用 int 调用的话会出错
void fun(char ch)
void fun(int* pi)// 或者其他指针

// 用 int 调用同样会出错
void fun(double db)
void fun(int* pi)// 或者其他指针

“嗯,很好。不过你还是漏了一种重要情况,”,Solmyr 补充道,“就是参数有缺省值的时候:”

// 调用时如果不给参数会出错
void fun(int i=10)
void fun()

“天哪!”,zero 看起来快要崩溃了,“居然有这么多模棱两可的陷阱,这叫我怎样发布我的函数?在文档里写:以下 153 种调用方式将导致编译错误吗?”

“不要这么紧张,”,Solmyr 好整以暇的说到,“重载函数的模棱两可现象不是不能避免的,办法有两个:一是用模板来代替重载,尤其是象你的 SomeFunc 这样 int 型和 double 型处理算法相同的情况;二是如果要用重载的话,尽可能保证函数的参数个数不同。”

“可是如果处理算法不一样,函数需要的参数个数又相同,那该怎么办?”

“很简单,加入‘无用的参数’,象这样:”

void SomeFunc(float db, int)
void SomeFunc(int i)

“第一个函数的第二个参数没有任何作用,所以你可以干脆不给它命名,只要声明一下有这个 int 型参数就可以了。文档里可以这样写:该参数是为今后升级预留的余地,调用时请传入 0 值。”

“ …… 你的文档里大概都是这一类的话吧 …… 啊!好痛!这回又是一本书!”,zero 被 Solmyr 突如其来的袭击击中,发出了悲惨的哀鸣。

“你得感谢先知 Scott Meyers,他的《 50 诫》轻而薄,我手上拿的若是一本教主 Bjarne Stroustrup 的《圣经》(注:指《The C++ Programing Language 3/e》一书,Bjarne Stroustrup 是 C++ 语言的设计者),你现在已经爬不起来了。”,Solmyr 再度披上了修养的伪装,不过言辞中仍然留着一点点杀气的痕迹 ……

“真是残暴的家伙 ……”,zero 小声嘟囔着。

“你说什么?”,杀气再度升高。

“不,不!我什么也没说!”,zero 连忙否认,试着转移话题,“啊!我懂了,要避免模棱两可的陷阱,一是用模板来替代重载,二是利用加入‘无用的参数’这一手段保证重载函数参数个数不同。这样就可以避开模棱两可的问题,是不是,Solmyr 老师?”。zero 很努力的装出天真无邪的样子。

“ …… 真是拙劣的演技 …… ”,Solmyr 心中暗想。“不完全,上述手段只能解决函数重载这一块而已,模棱两可问题涉及的情况要广泛的多,比如《 50 诫》中的例子:”

class B;// 前置声明

class A
{
public:
A(const B&);// A 可以根据 B 构造出来
};

class B
{
public:
operator A() const;// B 可以被转换为 A
};

“这两个类本身没有什么问题,但若是有个函数需要 A 的对象作为参数,传过去的却是个 B 的对象时:”

void f(const A&)
B b;
f(b);// Error! ambiguous call

“注意到这里面的问题了吗?有两种一样好方法可以完成转换,一是用 A 的构造函数以 b 为参数构造一个新的 A 类对象,而是调用 B 的转换函数将 b 转换为一个 A 类对象。编译器再度无法区分哪个转换更好,只能报错了。后面还有一个多重继承的例子,你自己看吧”

“这 …… 这 ……”,zero 刚刚建立起来的对回避陷阱的自信再度崩塌。

“要回避一切模棱两可的问题是不可能的,”,Solmyr 站起身来,“关键是了解它为什么会发生,怎样的情况容易诱发它,然后小心的加以处理,C++ 中很多问题都是如此。这块《 50 诫》的石板就留给你了,好好研读吧。哈哈哈哈!”,Solmyr 一边笑着一边离开了 zero ,背影看起来像是一位飘然远去的高人 ……

“什么呀!根本就只是一个性格残暴的家伙而已,装模做样 …… 啪!”,zero 话音未落,一个文件夹划破空气飞来,正中 zero 的面门。

“呜 ~ 我什么也没说 ~”,zero 无力的辨白,然而换来的只是旁边的座位上再度传来低低的窃笑声而已。zero 明白,今天他的形象算是彻底的毁了 ……

抱歉!评论已关闭.