“可加以重载的操作符(overloadable operators)”,啊哈,你一定爱死它了。它让你所定义的类型有着和 C++内建类型一样的语法,允许你在“操作符背后的支撑函数”内放置威力强大的手段,那是内建类型不可能有的待遇。当然,“可让诸如‘+’和‘==’符号做任何事情”这个事实,也意味你可能利用重载操作符写出一些令人难以理解的程序。成熟的 C++程序员知道如何驾驭操作符重载的强大威力,不落入令人费解的沉沦中。
可叹的是,稍有不慎,便向下沉沦。单自变量 constructors 及隐式类型转换操作符尤其麻烦,因为它们可以在没有任何外在迹象的情况下被调用。这可能会导致程序行为难以理解。另一类问题会在你将诸如&&和||等操作符重载之后发生,因为从“内建操作符”移转到“用户定制函数”所导致的各种语义敏感变化,很容易被忽略。最后,许多操作符和其他操作符之间有某种标准关系,但“操作符重载(operators overloading)”这个能力却使这种标准关系有被破坏的可能。
以下各个条款,我把焦点放在“重载操作符”被调用的时机、被调用的方法、它们的行为、它们应该如何与其他操作符产生关系,以及你如何夺取“重载操作符”的控制权。有了本章带给你的信息,你就可以像专家一样地将“重载操作符”玩弄于股掌之间了。
条款 5:对定制的“类型转换函数”保持警觉
C++允许编译器在不同类型之间执行隐式转换(implicit conversions)。继承了C 的伟大传统,这个语言允许默默地将char转换为 int,将 short 转换为double。这便是为什么你可以将一个 short 交给一个“期望获得 double”的函数而仍能成功的原因。C++还存在更令人害怕的转型(我指的是可能遗失信息的那种),包括将 int转换为 short,以及将 double(或其他东西)转换为 char。
你对这类转型无能为力,因为它们是语言提供的。然而当你自己的类型登场,你便有了更多的控制能力,因为你可以选择是否提供某些函数,供编译器拿来作为隐式类型转换之用。
两种函数允许编译器执行这样的转换:单自变量 constructors 和隐式类型转换操作符。所谓单自变量 constructors 是指能够以单一自变量成功调用的constructors。如此的 constructor 可能声明拥有单一参数,也可能声明拥有多个参数,并且除了第一参数之外都有默认值。下面是两个例子:
class Name {
public:
Name(const string &s); //可以把string转换为Name.
...
};
class Rational {
public:
Rational(int numerator = 0, //可以把int转换为Rational.
int denominator = 1);
...
};
所谓隐式类型转换操作符,是一个拥有奇怪名称的 member function:关键词operator 之后加上一个类型名称。你不能为此函数指定返回值类型,因为其返回值类型基本上已经表现于函数名称上。例如,为了让 Rational objects 能够被隐式转换为 doubles(这对掺杂有 Rational objects的混合型算术运算可能有用),你可能定义 class Rational 如下:
class Rational {
public:
...
operator double() const; //将Rational转换为double.
};
这个函数会在以下情况被自动调用:
Rational r(1, 2); //r的值是1/2
double d = 0.5 * r; //将r转换为double, 然后执行乘法运算
或许这一切对你而言都只是复习。那很好,因为我真正要解释的是,为什么最好不要提供任何类型转换函数。
根本问题在于,在你从未打算也未预期的情况下,此类函数可能会被调用,而其结果可能是不正确、不直观的程序行为,很难调试。
让我们先处理隐式类型转换操作符,因为它比较容易掌握。假设你有一个 class用来表现分数(rational numbers)。你希望像内建类型一样地输出 Rational objects内容。也就是说你希望能够这么做: