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

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

令 classes 支持隐式类型转换通常是个糟糕的注意。当然这条规定则有其例外,最常见的例外是在建立数值类型时。

假设你设计一个 class 用来表现有理数,允许整数 “ 隐式转换 ” 为有理数颇为合理。的确,它并不比 C++ 内置从 int 至double 的转换来得不合理,而还比 C++ 内置从 double 至 int 的转换来得合理些。
假设这样开始一个 Rational class:

class Rational{
public:
	Ratonal(int numerator = 0, 			// 构造函数刻意不为 explicit,
	int denominator = 1); 				// 允许 int-to-Rational 隐式转换
	int numerator() const;				// 分子(numerator)和分母(denominator)
	int denominator() const;			// 的访问函数(accessors)
private:
	...
};

你想要支持加减乘除运算等等,但你不确定是否该由 member 函数、non-member 函数,或者可能的话由 non-member friend 函数来实现它们。你的直觉可能会告诉你把它放在 class 内实现。

不管怎样,现将 operator* 写成 Rational 的成员函数:

class Rational{
public:
	...
	const Rational operator* (const Ratiaonal& r) const;
};

(tip: 如果你不确定为什么这个函数被声明为此种形式,也就是为什么它返回一个 const by-value 结果却接受一个 reference-to-const 实参,请参考条款 3、20和21。)
这个设计使你能够将两个有理数以最轻松自在的方式相乘:

Rational oneEighth(1, 8);
Rational oneHalf(1, 2);
Rational result = oneEighth * oneHalf;		// 很好
result = result * oneHalf;					// 很好

但你不满足,你希望它支持混合运算,也就是拿 Rationals 和…例如 ints 相乘。毕竟两数相乘更令人自然——即便是两个不同类型的数值。

然而当你尝试混合式算术,你发现只有一半行得通,令人难过:

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

这不是个好兆头。乘法一个满足交换律,不是吗?
当你以对应的函数形式重写上述两个例子,问题就一目了然了:

result = oneHalf.operator*(2);		// 很好
result = 2.operator*(oneHalf);		// 错误!

是的,oneHalf 是一个内含 operator* 函数的 class 对象,所以编译器 调用该函数,然而整数 2 并没有相应的 class,也就没有 operator* 成员函数。所以编译器会尝试寻找可被以下这般调用的 non-member operator* (也就是在命名空间内或在 global 作用域内):

result = operator* (2,oneHalf);

但本例中并不存在这样一个接受 int 和 Rational 作为参数的 non-member operator*,因此会查找失败。

再来看看先前成功的那个调用。注意其第二个参数是 2,但 Rational::operator* 需要的实参却是个 Rational 对象。这究竟怎么回事呢?

因为这里发生了所谓的隐式类型转换。编译器知道你正在传递一个 int,而函数需要的是 Rational,但它也知道只要调用 Rational 构造函数并赋予你所提供的 int,就可以变出一个适当的 Rational 来。于是它就这样做了。换句话说此一调用有点像如下所示:

const Rational temp(2);				// 根据 2 建立一个暂时性的 Rational 对象。
result = oneHalf * temp;			// 等同于 oneHalf.operator*(temp);

当然,只因为这是 non-explicit 构造函数,编译器才会这样做。如果 Rational 构造函数是 explicit,以下语句均不能通过编译:

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

这就很难让 Rational class 支持混合式算术运算了,不过至少上述两个句子的行为从此一致。

然而你的目标不止在一致性,也要支持混合式算术运算,也就是希望有个设计能让以上语句均通过编译。

解决办法:
让 operator* 成为一个 non-member 函数,也就是允许编译器在每一个实参上执行隐式类型转换:

class Rational{ ... };								// 不含 operator*
const Rational operator*(const Rational& l,
						const Rational& r)			// 	现在成了 non-member 函数
				{
				return Rational( l.numerator() * r.numerator(), l.denominator() * r.denominator() );
				}
Rational oneFourth(1, 4);
Rational result;
result = oneFourth * 2;			// 没问题
result = 2 * oneFourth;			// 万岁,通过编译啦!

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

答案是否定的,因为 operator* 可以完全藉由 Rational 的 public 接口完成任务,上面的代码已经表明此种做法。

请记住:

  • 如果你需要为某个函数的所有参数(包括被 this 指针所指的那个隐喻参数)进行类型转化,那么这个函数必须为 non-member 函数。
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值