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

什么是类型转换函数?
1)转换构造函数可以将一个指定类型的数据转换为类的对象
2)类型转换函数的作用是将一个类的对象转换成另一类型的数据
3)一个类中同时有转换构造函数和类型转换函数将产生二义性,程序报错

举一个复数的例子就明白了:

class Complex
{
public:
   Complex( ){real=0;imag=0;}
   Complex(double r,double i){real=r;imag=i;}
   operator double( ) {return real;} //类型转换函数
private:
   double real;
   double imag;
};

int main( )
{ 
   Complex c(3,4));
   double d;
   d=2.5+c;//要求将一个double数据与Complex类数据相加
   cout<return 0;
}

当缺乏类型转换函数时,d=2.5+c就会报错。因为d是double类型,而c是一个Complex类。而自定义类型转换函数之后,上述main函数将会隐式调用operator double( ),将Complex类对象转换为double型数据。

那么现在解释条款5,为什么最好不要提供任何类型转换函数
根本问题在于,在你从未打算也未预期的情况下,此类函数可能会被调用,而其结果可能是不正确、不直观的程序行为,很难调试。

看下面这一段代码:

class Rational
{
public:
    Rational(double a=0,double b=1){
        numerator=a;
        denominator=b;
    }
    operator double( ){
        return numerator/denominator;
    } //类型转换函数
private:
   double numerator;
   double denominator;
};

int main( )
{ 
   Rational r(1,2);
   cout<return 0;
}

这段代码的Rational类里,我们忘了为它写一个operator<<,然而却在main函数里调用了<<,或许你会认为上述打印不会成功。但是你错了,编译器面对上述动作,发现不存在任何operator<<可以接受一个Rational,但它会想尽各种办法(包括找出一系列可接受的隐式类型转换)让函数调用动作成功。这虽然不至于造成灾难,却显示了隐式类型转换操作符的缺点:它们的出现可能导致错误(非预期)的函数被调用。

解决办法就是以功能对等的另一个函数取代类型转换操作符。
上述代码的类型转换函数可改成:

double asDouble() const;

如此的member function也因此必须被明确调用:

cout<//错误!Rational没有operator<<
cout<//可以!以double的形式输出r

大部分时候,“必须明白调用类型转换函数”虽然带来些许不便,却可因为“不再默默调用那些其实并不打算调用的函数”而获得弥补。

通过单自变量constructor完成的隐式转换,较难消除。此外,这些函数造成的问题在许多方面比隐式类型转换操作符的情况更不好对付。

举个例子,考虑一个针对数组结构而写的class template:

template<class T>
class Array
{
    public:
        Array(int lowBound,int highBound);  //身为一个双自变量constructor,没有资格成为类型转换函数 
        Array(int size);    //可以用来作为一个类型转换函数 
        T& operator[](int index);
};

例如,考虑一个用来比较Array对象进行比较动作的函数,以及一小段代码:

bool operator==(const Array<int>& lhs,const Array<int>& rhs);
int main()
{
    Array<int> a(10);
    Array<int> b(10);
    for(int i=0;i<10;++i)
    {
        if(a==b[i]) //哎哟!"a"应该是 "a[i]"才对 
        {
            //do sth.
        }
        else
        {
            //do sth. else
        }
    }
    return 0;
}

我希望编译器发挥挑错功能,将a(实际上应该为a[i])挑出来,但它却没有报错。因为它看到的是一个operator==函数被调用,夹带着类型为Array< int >的自变量和类型为int的自变量b[i]。
虽然没有这样的operator==可以被调用,但编译器注意到,只要调用Array< int > constructor(需要一个int作为自变量),它就可以将int转为Array< int > object。
于是,循环的每一次迭代都拿a的内容和一个大小为b[i]的临时数组(其内容想必未定义)做比较。这种行为不仅不令人满意,而且每走过这个循环,我们都必须产生和销毁一个临时的Array< int > object。

而我们可能真的需要提供一个单自变量的constructor给我们的客户使用。
如何阻止编译器不分青红皂白地调用这样的constructor?关键词explicit
这个特性之所以被导入,就是为了解决隐式类型转换带来的问题,只要将constructor声明为explicit,编译器便不能因隐式类型转换的需要而调用它们。不过显示类型转换仍是允许的。

如果编译器尚不支持关键词explicit,那么就只能通过以下做法阻止单变量constructor成为隐式类型转换函数:

template
class Array
{
    public:
        class ArraySize //这个class是新加入的
        {
            public:
                ArraySize(int numElements): theSize(numElements){}
                int size() const{
                    return theSize;
                }
            private:
                int theSize;
        };
        Array(int lowBound,int highBound);
        Array(ArraySize size);  //注意这个新的声明 
};

以一个int自变量构造起一个Array对象这个事实仍然可靠有效,而再次考虑上述main函数的代码,“if(a == b[i]) …”如今形成了一个错误。因为那将调用两个用户定制转换行为,一个将int转换为ArraySize,另一个将ArraySize转换为Array。如此的转换程序是禁止的。

类似ArraySize这样的classes,往往被称为proxy classes,它的每一个对象都是为了其他对象而存在的。proxy objects让你得以超越外观形式,进而控制你的软件行为,是一项很值得学习的技术。

所以不要提供转换函数,除非你确定需要它们。

你可能感兴趣的:(《More,Effective,C++》笔记)