天天看点

More Effective C++ 05:谨慎定义类型转换函数

C++编译器允许把

char

隐式转换为

int

和从

short

隐式转换为 double。因此当你把一个 short 值传递给准备接受 double 参数值的函数时,依然可以成功运行。

你对这些类型转换是无能为力的,因为它们是语言本身的特性。不过当你增加自己的类型时,你就可以有更多的控制力,因为你能选择是否提供函数让编译器进行隐式类型转换。

有两种函数允许编译器进行这些的转换:单参数构造函数和隐式类型转换运算符。单参数构造函数是指只用一个参数即可以调用的构造函数。该函数可以是只定义了一个参数,也可以是虽定义了多个参数但第一个参数以后的

所有参数都有默认值。以下有两个例子:

class Name 
{ 
public: 
 Name(const string& s); // 转换 string 到 Name
 ... 
}; 
class Rational  // 有理数类 
{
public: 
	Rational(int numerator = 0,int denominator = 1); //转换 int 到有理数类   
	
 ...
};
           

例如为了允许

Rational

(有理数)类隐式地转换为

double

类型你可以如此声明 Rational 类:

class Rational 
{ 
public: 
 ... 
	operator double() const; // 转换 Rational类成double 类型
};
           

在下面这种情况下,这个函数会被自动调用:

Rational r(1, 2);
double d = 0.5 * r;// 转换 r 到 double然后做乘法。
           

假设你有一个如上所述的

Rational

类,你想让该类拥有打印有理数对象的功能,就好像它是一个内置类型。因此,你可能会这么写:

Rational r(1, 2); 
cout << r; // 应该打印出"1/2"
           

假设你忘了为

Rational

对象定义

operator<<

。你可能想打印操作将失败,因为没有合适的的

operator<<

被调用。但是你错了。当编译器调用

operator<<

时,会发现没有这样的函数存在,但是它会试图找到一个合适的隐式类型转换顺序以使得函数调用正常运行。类型转换顺序的规则定义是复杂的,但是在现在这种情况下,编译器会发现它们能调用

Rational::operator double

函数来把 r 转换为double类型。所以上述代码打印的结果是 一个浮点数,而不是一个有理数

More Effective C++ 05:谨慎定义类型转换函数

解决方法:

用不使用语法关键字的等同的函数来替代转换运算符。例如为了把

Rational

对象转换为

double

,用

asDouble

函数代替

operator double

函数:

More Effective C++ 05:谨慎定义类型转换函数
More Effective C++ 05:谨慎定义类型转换函数

在多数情况下,这种显式转换函数的使用虽然不方便,但是函数被悄悄调用的情况不再会发生,这点损失是值得的。一般来说,越有经验的C++程序员就越喜欢避开类型转换运算 符。

比如的

string

类型没有包括隐式地从

string

转换成C风格的

char*

的功能,而是定义了一个成员函数

c_str

用来完成这个转换。

通过单参数构造函数进行隐式类型转换更难消除。而且在很多情况下这些函数所导致的问题要甚于隐式类型转换运算符。

比如:

template<class T> 
class Array 
{ 
public: 
	 Array(int lowBound, int highBound); 
	 Array(int size); 
	 T& operator[](int index); 
 ... 
};
           

第一个构造函数允许调用者确定数组索引的范围,例如从 10 到 20。它是一个两参数构造函数,所以不能做为类型转换函数。

第二个构造函数让调用者仅仅定义数组元素的个数(使用方法与内置数组的使用相似),不过不同的是它能做为类型转换函数使用,能导致无穷的痛苦。

例如比较 Array对象,部分代码如下:

bool operator==( const Array<int>& lhs, const Array<int>& rhs); 
Array<int> a(10); 
Array<int> b(10); 
... 
for (int i = 0; i < 10; ++i) 
 if (a == b[i]) // 哎呦! "a" 应该是 "a[i]" 
 { 
	 do something for when 
	 a[i] and b[i] are equal; 
 } 
 else 
 { 
	 do something for when they're not; 
 }
           

我们想用 a 的每个元素与 b 的每个元素相比较,但是当录入 a 时,我们偶然忘记了数组下标。当然我们希望编译器能报出各种各样的警告信息,但是它根本没有。因为它把这个调用看成用

Array<int>

参数(对于 a)和

int

(对于 b[i])参数调用

operator==

函数

然而没有

operator==

函数是这样的参数类型,我们的编译器注意到它能通过调用

Array<int>

构造函数能转换

int

类型到

Array<int>

类型,这个构造函数只有一个

int

类型的参数。然后编译器如此去编译,生成的代码就象这样:

for (int i = 0; i < 10; ++i) 
 if (a == static_cast<Array<int>>(b[i])) ...
           

每一次循环都把a的内容与一个大小为 b[i]的临时数组(内容是未定义的)比较。这不仅不可能以正确的方法运行,而且还是效率低下的。因为每一次循环我们都必须建立和释 放

Array<int>

对象

容易的方法是利用一个最新编译器的特性,

explicit

关键字。为了解决隐式类型转换而特别引入的这个特性,它的使用方法很好理解。构造函数用

explicit

声明,如果这样做,编译器会拒绝为了隐式类型转换而调用构造函数。显式类型转换依然合法:

template<class T> 
class Array 
{ 
public: 
... 
	explicit Array(int size);
... 
};
           

总结:

让编译器进行隐式类型转换所造成的弊端要大于它所带来的好处,所以除非你确实需要,不要定义类型转换函数

继续阅读