一. 内容
-
在导读中提过,令 classes 支持隐式转换是一个糟糕的主意,当然这有
例外
。比如当你建立一个数值类型,允许整数隐式转换为有理数似乎是合理的。好比 C++ 允许 int 隐式转换为 double,double 隐式转换为 int。 -
举个例子,假如你要编写一个分数 Rational 类。
class Rational { public: Rational(int mNumerator = 0, int mDenominator = 1): Numerator(mNumerator), Denominator(mDenominator) {} public: int GetNumerator() const { return Numerator; } int GetDenominator() const { return Denominator; } public: Rational operator*(const Rational& Other) const { return Rational(GetNumerator() * Other.GetNumerator(), GetDenominator() * Other.GetDenominator()); } private: int Numerator; int Denominator; };
-
对于分数的乘法实现,你可能在考虑是由 member 函数,还是 non-member,friend 函数,但基于面向对象的精神,你直觉告诉你,
可以把实现放到类中重载 operator*
。尽管在条款23反直觉的主张 non-member 也许比 member 函数更符合面向对象守则。但我们先放一放。在类中重载 operator*,你可以轻易的实现
const Rational TempOne(1, 8); const Rational TempTwo(1, 2); Rational Result = TempOne * TempTwo; //很好 Result = Result * TempOne; //很好
但不够,你可能还希望支持
混合运算
,也就是拿 Rational 与其他 int 相乘。Result = Result * 2;//很好,2隐式转换为Rational Result=2*Result;//错误 //写成函数调用可以看出问题 //Result=Result.operator*(2); //Result=2.operator*(Result);
但很快你发现,int * Rational 出现错误,但我们自然的认为
乘法应该符合交换律
。当你写成函数调用时,可以看出问题,Result=2.operator*(Result),2并不是一个class,也就每不存在 operator* 成员函数。编译器会尝试在全局作用域寻找这样的 non-member 函数 operator*(2,Result),但本例并不存在,所以查找失败。这中间有一点需要注意,为什么前者可以通过,而后者不可以通过?因为前者发生了隐式转换,编译器知道你传递了2(int),而函数需要的是 Rational,但
它也知道只要调用 Rational 构造函数并赋予你所提供的2(int),就可以变出一个适当的 Rational 出来
,于是它就那样做了。当然这是当你没有使用 explicit 关键字的情况下,因为explicit会阻止构造函数或转换函数进行隐式转换
。但是为什么后者的 2(int)不能进行隐式转换,注意,只有
当参数处于参数列时,这个参数才能进行隐式转换
。正如上面所述,必须在其他作用域存在 operator(Rational,Rational) 时,2才能被作为参数,合理的进行隐式转换,否则它的地位相当于被调用函数的那个对象。 -
那么怎样支持混合式运算呢?答案呼之欲出,拨云见日:使用 non-member 函数,使其每个实参可以进行隐式转换。
inline Rational operator*(const Rational& RationalOne,const Rational& RationalTwo){ return Rational(RationalOne.GetNumerator() * RationalTwo.GetNumerator(), RationalOne.GetDenominator() * RationalTwo.GetDenominator()); }
这样混合式运算就成功通过编译并且运行成功。
-
这是一个快乐的结局,但还有一点需要关心。non-member operator*是否有必要成为 friend 函数呢?就本例而言,答案是否定的,
因为 Rational 提供的 public 接口已经可以完成所有的任务
。这也给我们一个提示:member函数的反面是 non-member 函数,而不是 friend 函数。大多 C++ 程序员假如,如果一个函数不是一个 member 函数,那么它就该是一个 friend。这往往是不必要的,而且无论何时,都尽量避免 friend 函数
,因为就像真实世界里那样,朋友带来的麻烦往往多过其价值。 -
本条款蕴含真理,但不是全部的真理。特别是当你从
Object-oriented C++
转移到Template C++
,将 Rational 设计成 Rational Template 时,又会有新的争议,问题,解法。
二. 总结
- 如果你需要为某个函数的所有参数(包括被 this 指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是个 non-member。