5. 对定制的“类型转换函数”保持警觉

C++中允许编译器在不同类型中执行隐式转化,例如默默地将char转化为int,将short转化为double等等,这些是语言提供的。现在当你写自己的类型时,你可以选择是否提供某些函数,供编译器用作隐式类型转化之用。如定义一个类类型,是否允许其它类型转化为此类类型,我们可以操控的。

可以通过两种函数允许编译器执行这样的隐式转化:单自变量constructor 和隐式类型转化操作符。

单自变量constructor :指的是以单一自变量成功调用的constructor,这样的constructor可能只有一个单一参数,也可能有多个参数,而其他参数都有默认值。如下面两个例子:

class CName{
    public:
        CName(const string& s);      //ctor声明时只有一个自变量
    //...
};
class CRational{
    public:
       CRational(int numerator = 0, int denominator = 1);      //ctor声明时有两个自变量,但其他有默认值
    //...
};
//上面的ctor可以用来作为隐式类型转化函数
CName stu = "hazirguo";         //可以将string类型转化为CName类型
CRational doub = 3;             //可以将int 类型转化为CRational类型

隐式类型转化操作符:这是一个成员函数,由关键词operator 后加上一个类型名称。如下例:

class CRational{
    public:
        CRational(int numerator, int denominator);
        operator double() const                       //可以完成将CRational类转化为double类型
        {   return numerator * 1.0 / denominator;   }
        //...
};
//下面的代码会调用上面的转化函数

CRational r(1, 2); // r = 1/2、

double d = 0.5 * r; // 将r转化为double,再相乘

但是,最好不要提供任何类型转化函数!!!因为在你从未预期的情况下,此类函数就可能被调用,而结果显然不是你想要的,但是此类错误往往是很难调试的。分别举例说明之:

例一:

class CArray{
public:
	CArray(int lowBound, int highBound);       //两个参数的ctor,不可能作为隐式转化函数
	CArray(int size);                          //单变量ctor,可能作为隐式转化函数使用
	int operator[] (int index);
	//...
};
bool operator == (const CArray& lhs, const CArray& rhs);       //重载 == 函数
CArray a(10);
CArray b(10);
for (int i = 0; i < 10; i++)
{
	if (a == b[i])           //原意是a[i] == b[i],但此处编译器却为报错
	{   /* do something when a[i]==b[i]  */   }   // (***)
        else
        {  /* ...  */     }
}

之所以没有报错的原因是:a == b[i]的一边是CArray型,一边是int型,而重载operator == 函数带有两个CArray型参数,编译器试图将int转化为CArray型是行的通的,因为CArray有个单自变量的ctor,可以将int转化为CArray型。但很显然,这样的话,结果不是我们预期的那样!

解决的方法:只要将ctor声明为explicit就行了,编译器便不能因隐式转化的需要调用它们。不过显示类型转化仍然是可行的:

class CArray{
public:
   //...
   explicit CArray(int size);      //这里使用了关键字explicit
   //...
};
CArray a(10);
CArray b(10);
if (a == b[i])         //error! 无法将int隐式转化为CArray型
if (a == static_cast<CArray> (b[i]))          //right!显示转化仍然可行


例二:

class CRational{
    public:
        CRational(int numerator, int denominator);
        operator double() const                       //可以完成将CRational类转化为double类型
         {   return numerator * 1.0 / denominator;   }
        //...
};
CRational r(1, 2);
cout << r;          //原意输出1/2,但却输出了0.5

上面程序并没有定义operator << 可以接受CRational,但编译器却没有报错,却输出了一个double型的数0.5。原因在于,编译器会想尽各种方法让函数转化成功,本例中只要调用CRational::operator double 即可将CRational转化为double型,于是上述代码将r以非分数形式输出。显然,这也不是我们想要的结果!!

解决方法:以功能对等的另一个函数取代类型转化操作符。本例中,为了将CRational转化为double,不妨以一个名为asDouble的函数取代operator double:

class CRational{
    public:
        CRational(int numerator, int denominator);
        double asDouble() const ;        //将CRational 转化为double
};
CRational r(1, 2);
cout << r;            //error! CRational 没有operator <<
cout << r.asDouble();      //right!

小结: 一般而言,愈有经验的C++程序员愈可能避免使用类型转化操作符。

参考文献: 《More Effective C++ 35个改善编程与设计的有效方法 中文版

你可能感兴趣的:(编程,String,Class,语言,Constructor,编译器)