Item 5: 对定制的“类型转换函数”保持警觉
C++从C语言那继承了其隐式转换,可以实现不同类型之间的转换,且C++的隐式转换还存在更令人害怕的转型,有可能出现数据丢失。对于这些,你无能为力,因为这都是语言自身提供的。然而当你的自定义类型登场时,你可以通过提供某些函数,来作为编译器的转型之用。但是为什么最好不要提供任何类型转换函数呢?
原因在于:在你为打算也为预期的情况下,此类函数可能会被调用,而其结果可能不正确、不直观,很难调试。
在C++中,编译器会通过两种方式来实现类型的隐式转换:
1、使用隐式类型转换符,即关键词operator之后加上一个类型名称,如operator double(),注意:你不能为此函数指定返回值类型;
2、通过单变量的constructor,包括只有一个参数的constructor,也包括多个参数,但除了第一个参数外其他的参数都是有缺省值的constructor,如Constructor_Name(Type name)和Constructor_Name(Type1 name1,Type2 name2=Default_Value)。
下面我们来分别介绍这两种方式别调用的调节以及应对措施:
第一种:使用operator重载函数
当为自定义类提供operator double()重载函数时,该重载函数会在以下情况被调用:ClassType temp(1,2); double d=0.5*temp;
大概转换过程是:对象temp调用operator double()转换为double temp=1/2;然后与0.5相乘,最后把结果赋值给变量d。
这种情况比较好解决:我们只要以功能对等的另一个函数取代类型转换操作符,如将operator double() 转换为 double asDouble()即可。
这样,当需要转型时,需要显示的调用该member function才能实现。
第二种:通过单变量constructor
通过单自变量constructor完成的隐式转换较难消除。如下例所示:
template <class T>
class Array{
public:
Array(int size);
...
T& operator[] (int index);
};
Array<int> a(10);
Array<int> b(10);
for(int i=0; i<10; i++) if(a == b[i]){do something ...} else {do other things...}
因为代码中把a[i]写成了a,编译器原本应该给出错误或者警告提示的,但是C++编译器很聪明,它会想尽办法找到一个合适的函数以使程序顺利执行,因此它发现只要将b[i]通过constructor就可以转换为Array<int>类型的对象,于是它就放手去做了。
于是问题就出现了,循环的每次比较都发生在a的内容和一个大小为b[i]的临时数组做比较,结果可想而知。且这样做因为每次都要构造和析构一个临时对象,所以十分没效率。
那该怎么解决呢?
1、如果你的编译器支持explicit关键词,就容易解决,只要将constructor做如下声明即可;
explicit Constructor_Name(Type var);这样编译器就不会因隐式转换而调用它了,不过显式类型转换仍是允许的哦。
2、如果你的编译器不支持explicit关键词,那么只能走弯路,利用C++的规则中的一条:没有任何一个转换程序可以内含一个以上的“用户定制转换行为”。
我们可以将Array类的constructor进行变换,产生一个新类ArraySize类,该类只用于表明数组的大小。即
class Array {
public:
class ArraySize {
public:
ArraySize(int num):theSize(num);
...
};
Array(ArraySize size);
}
如此一来,当我们定义Array类的对象时:Array<int> a(10);编译器会发现可以利用ArraySize类的constructor实现int->ArraySize object的转换,它毫不犹豫地做了,事实也证明这样做是对的,Array类的constructor的确需要一个ArraySize类的object。对于这个情况是我们需要的隐式转换,但是对于其他情况呢?
我们来看另一种情况,同上例,if(a == b[i]),我们将a[i]写成了a,此时会不会发生隐式转换呢?答案是否定的。
因为对于上面的情况,如果可以成功执行,需要两个转换:1)通过ArraySize类的constructor将int b[i]转换为ArraySize的对象;2)再通过Array类的constructor将ArraySize类的对象转换为Array<int>类型的对象。由C++规则可知,这样的转换程序是禁止的。
允许编译器执行隐式类型转换,害处将多过好处,因此不要提供转换函数,除非你确定你真的需要他们。