Effective C++之条款24:若所有参数皆需类型转换,请为此采用non-member函数

声明:

  1. 文中内容收集整理自《Effective C++(中文版)第三版》,版权归原书所有。
  2. 本内容在作者现有能力的基础上有所删减,另加入部分作者自己的理解,有纰漏之处敬请指正。

条款24:若所有参数皆需类型转换,请为此采用non-member函数

Declare non-member functions when type conversions should apply to all parameters.

直接进入正题。

假设设计一个class用来表现Rational,允许整数“隐式转换为”有理数似乎颇为合理。

设计一个如下Rational类:

class Rational
{
public:
    Rational(int numerator = 0,int denominator = 1) : x(numerator), y(denominator){}
    //构造函数不为explicit,允许int-to-Rational隐式转换 
    int numerator() const; //分子
    int denominator() const; //分母
    const Rational operator*(const Rational& rhs) const //有理数的乘法
    {
    	return Rational(x*rhs.x, y*rhs.y);
    }
private:
	int x;
	int y;
};

我们可以轻松自在地将两个有理数相乘:

Rational oneEight(1, 8);
Rational oneHalf(1, 2);
Rational result = onHalf*oneEight;
result = result*oneEight;

但是当我们尝试混合运算,就会发现有问题:

result = oneHalf * 2; //很好
result = 2 * oneHalf; //错误

当以对应的函数形式重写上面的两个式子,问题所在就一目了然了:

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

oneHalf是一个内含operator*函数的的class 的对象,所以编译器成功调用该函数,然而整数2不是class的对象,所以也就没有operator*()函数。编译器也会会去尝试寻找可以被以下这般调用的non-member operator*()函数(也就是在命名空间或者全局作用域内),但如果这个代码中并没有符合要求(即接受一个int 和Rational作为参数的non-member operator*()),所以会查找失败。

result = operator*(2, oneHalf);

我们再来看看成功的那个调用。operator*()需要的实参是个Ratioal对象,而也可以接收一个int型呢?

因为这里发生了隐式类型转换(implicit type conversion),编译器知道你正在传递一个int,它也知道调用Ratianal的构造函数就可以构造出一个适当的Rational来。于是它就那样做了。就像这样的动作:

const Rational temp(2);
result = oneHalf*temp;

当然因为我们的构造函数声明为non-explicit,编译器才会这样做。而如果将构造函数声明为explicit的话,上面的两句都不能通过编译。

那么为什么即使Rational的构造函数为non-exolicit,仍然有一个可以通过编译,而另一个不能呢?

结论是,只有当参数被列于参数列表内,这个参数才是隐式类型转换的合格参与者。被调用之成员函数所隶属的那个对象——this对象这个隐喻参数,绝不是隐式转换的合格参与者。

我们一定要支持混合运算,可行之道终于拨云见日:让operator*()成为一个non-member函数,俾允许编译器在每一个实参上执行隐式类型转换。

class Rational
{
public:
	Rational(int numerator = 0, int denominator = 1) : x(numerator), y(denominator) {}
	//构造函数不为explicit,允许int-to-Rational隐式转换 
	int numerator() const //分子
	{
		return x;
	}
	int denominator() const //分母
	{
		return y;
	}
private:
	int x;
	int y;
};
const Rational operator*(const Rational& lhs, const Rational& rhs)
{
	return Rational(lhs.numerator()*rhs.numerator(), lhs.denominator()*rhs.denominator());
}

这个时候我们就可以愉快地进行如下操作了:

Rational oneFourth(1, 4);
result = 3*oneFourth; //万岁,终于通过编译了!!!

这当然是个快乐的结局,不过还有一点需要操心:operator*是否应该成为Rational class的一个friend函数呢?

对于本例来说是否定的,因为operator*可以藉由public接口完成访问私有成员的目的。无论何时如果可以避免friend函数就该避免,而不能够只因为函数不该成为member就自动让它成为friend。

请记住:

如果你需要为某个函数的所有参数(包括被this指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是个non-member。

猜你喜欢

转载自blog.csdn.net/longmenshenhua/article/details/88937395