建议17:提防隐式转换带来的麻烦
在C/C++语言的表达式中,允许在不同类型的数据之间进行某一操作或混合运算。当对不同类型的数据进行操作时,首先要做的就是将数据转换成为相同的数据类型。C/C++语言中的类型转换可以分为两种,一种为隐式转换,而另一种则为建议11中提及的显式强制转型。显式强制转型在某种程度上还有一定的优点,对于编写代码的人来说使用它能够很容易地获得所需类型的数据,对于阅读代码的人来说可以从代码中获知作者的意图。而隐式转换则不然,它让发生的一切变得悄无声息,在编译时这一切由编译程序按照一定规则自动完成,不需任何的人为干预。
存在大量的隐式转换也是C/C++常受人诟病的焦点之一。隐式转换虽然带来了一定的便利,使编码更加简洁,减少了冗余,但是这些并不足以让我们完全接受它,因为隐式转换所带来的副作用不可小觑,它通常会使我们在调试程序时毫无头绪。就像下面的代码片段所示:
|
上述代码片段中的函数调用不会出现任何错误,编译器给出的仅仅是一个警告。可是细心的程序员一眼就能看出问题:函数Function(char c)的参数c是一个char型,256绝不会出现在其取值区间内。但是编译器会自动地完成数据截断处理。编译器悄悄完成的这种转换存在着很大的不确定性:一方面它可能是合理的,因为尽管类型long大于char,但para中很可能存放着char类型范围内的数值;另一方面para的值的确可能是char无法容纳的数据,这种“暗地里的勾当”一不小心便会造成一个非常隐蔽、难以捉摸的错误。
C/C++隐式转换主要发生在以下几种情形。
基本类型之间的隐式转换
|
在编译这段代码时,编译器会按照规则自动地将ival转换为与dval相同的double类型。C语言规定的转换规则是由低级向高级转换。两个通用的转换原则是:
(1)为防止精度损失,类型总是被提升为较宽的类型。
(2)所有含有小于整型类型的算术表达式在计算之前其类型都会被转换成整型。
这两点在C++中依旧有效,这已无须多言。它最直接的害处就是有可能导致重载函数产生二义性,如下所示:
|
参数0.5应该转换为ival还是fval?这是编译器没法搞明白的一个问题。
T* 指针到 void* 的隐式转换
在C语言中,标准允许T*与void*之间的双向转换,这也就间接导致了各种数据类型之间的隐式转换是被允许的,无论是从低级到高级,还是从高级到低级。这样的转换存在着太多的不安全因素,所以到了C++中,双向变单向,只允许T*隐式地转换为void*了,示例代码如下所示:
|
non-explicit constructor接受一个参数的用户定义类对象之间隐式转换先看如下代码:
|