Item 24: 当类型转换应该用于所有参数时,声明为非成员函数

当类型转换应该用于所有参数时,声明为非成员函数

最好不要提供隐式的类型转化, 但这条规则也存在特例,比如当我们需要创建数字类型的类时。正如 double 和int 能够自由地隐式转换一样, 我们的数字类型也希望能够做到这样方便的接口。

当然这一节讨论的问题不是是否应当提供隐式转换,而是如果运算符的所有“元”都需要隐式转换时,请重载该运算符为友元函数。
通过运算符重载来扩展用户定义类型时,运算符函数可以重载为成员函数,也可以作为友元函数。 但如果作为了成员函数,*this 将被作为多元操作符的第一元,这意味着第一元不是重载函数的参数,它不会执行类型转换。 仍然拿有理数类作为例子,下面的 Rational 类中,将运算符 * 重载为成员函数:

class Rational {
	int n, d;
public:
	Rational(int num = 0, int denom = 1) :n(num),d(denom){}
	int numerator() const { return n; }
	int denominator() const { return d; }
	const Rational operator*(const Rational& rhs) const
	{
		return Rational(this->d*rhs.d, this->n*rhs.n);
	}
};

我们看下面的运算符调用能否成功:

Rational oneHalf(1, 2);
 
Rational result = oneHalf * oneHalf;   //@ OK
result = oneHalf * 2;                  //@ OK
result = 2 * oneHalf;                  //@ Error

第一个运算符的调用的成功是很显然的。我们看第二个调用:

当编译器遇到运算符 * 时,它会首先尝试调用:

result = oneHalf.operator*(2);

编译器发现该函数声明(它就是定义在Rational类中的方法)存在, 于是对参数2进行了隐式类型转换(long->Rational)。所以第二个调用相当于:

Rational tmp(2);
result = oneHalf.operator*(tmp);

将 Rational 的构造函数声明为 explicit 可以避免上述隐式转换,这样第二个调用也会失败。

对于第三个调用,编译器仍然首先尝试调用:

result = 2.operator*(oneHalf);

2 属于基本数据类型,并没有成员函数 operator*。于是编译器再尝试调用非成员函数的运算符:

result = operator*(2, oneHalf);

再次失败,因为并不存在与 operator*(long, Rational) 类型兼容的函数声明,所以产生编译错误。 但如果我们提供这样一个非成员函数:

const Rational operator*(const Rational& lhs, const Rational& rhs);

这时候第一个参数也可以进行隐式转换。第三个调用(result = 2 * oneHalf)便会成功,该表达式相当于:

Rational tmp(2);
result = operator*(tmp, oneHalf);

总结

  • 如果你需要在一个函数的所有参数(包括被 this 指针所指向的那个)上使用类型转换,这个函数必须是一个非成员。
posted @ 2020-02-08 20:47  刘-皇叔  阅读(124)  评论(0编辑  收藏  举报