C_and_CPP 版 (精华区)
发信人: xuxian (呵呵), 信区: C_and_CPP
标 题: Solmyr 的小品文系列之二:模棱两可的陷阱
发信站: 哈工大紫丁香 (Wed Aug 27 20:03:49 2003)
Elminster(原作)
为什么会这样?!”,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 嘴边招牌式的坏笑时明白了过来:自己又一次被 S
olmyr 设计了。
“嗯,明白了这一点,我们就可以展开进一步的讨论了”,Solmyr 开始转入正题,“还记
得上次我说过上面的 1.2 是什么吗?”
zero 露出了回忆的表情:“嗯 …… 1.2 是‘写在代码里的常量’…… 应该是一个 dou
ble 类型常量。”
“这就是问题所在:编译器看到这个调用函数的请求,会去寻找你的重载函数中哪个函数
能够匹配这个调用请求给出的参数,结果它发现没有一个函数的参数是 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 型和 doub
le 型处理算法相同的情况;二是如果要用重载的话,尽可能保证函数的参数个数不同。”
“可是如果处理算法不一样,函数需要的参数个数又相同,那该怎么办?”
“很简单,加入‘无用的参数’,象这样:”
void SomeFunc(float db, int)
void SomeFunc(int i)
“第一个函数的第二个参数没有任何作用,所以你可以干脆不给它命名,只要声明一下有
这个 int 型参数就可以了。文档里可以这样写:该参数是为今后升级预留的余地,调用时
请传入 0 值。”
“ …… 你的文档里大概都是这一类的话吧 …… 啊!好痛!这回又是一本书!”,zero
被 Solmyr 突如其来的袭击击中,发出了悲惨的哀鸣。
“你得感谢先知 Scott Meyers,他的《 50 诫》轻而薄,我手上拿的若是一本教主 Bjar
ne Stroustrup 的《圣经》(注:指《The C++ Programing Language 3/e》一书,Bjarn
e 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 明白,今天他的形象算是彻底的毁了 ……
--
※ 来源:.哈工大紫丁香 bbs.hit.edu.cn [FROM: 202.107.117.15]
Powered by KBS BBS 2.0 (http://dev.kcn.cn)
页面执行时间:3.148毫秒