总结:
如果你需要在一个函数的所有参数(包括被 this 指针所指向的那个)上使用类型转换,这个函数必须是一个非成员。
让一个类支持隐式类型转换通常是一个不好的主意。当然,这条规则有一些例外:在创建数值类型时。例如,如果你设计一个用来表现有理数的类,允许从整数到有理数的隐式转换:
class Rational {
public:
Rational(int numerator = 0, int denominator = 1);
// 非explicit,允许int-to-Rational隐式转换int-to-Rational
int numerator() const; // 分子和分母的访问函数
int denominator() const;
private:
...
};
应该支持算术运算,比如加法,乘法等等,但
不能确定是通过成员函数、非成员函数、还是非成员的友元函数来实现它们。当你摇摆不定的时候,你应该坚持面向对象的原则。于是有理数的乘法与Rational类相关,所以在Rational类的内部实现有理数的operator*似乎更加正常。我们先让operator*成为Rational的一个成员函数:
class Rational {
public:
...
const Rational operator*(const Rational& rhs) const; //operator*作为成员函数
};
Rational oneEighth(1, 8);
Rational oneHalf(1, 2);
Rational result = oneHalf * oneEighth; // fine
result = result * oneEighth; // fine
这个设计让你在有理数相乘时不费吹灰之力,但你还希望支持混合模式的操作,以便让 Rational能够和其它类型(如int)相乘。毕竟两个数相乘很正常,即使它们碰巧是不同类型的数值:
result = oneHalf * 2; // fine
result = 2 * oneHalf; // error
只有一半行得通,但乘法必须是可交换的。当以对应的函数形式重写上述两个式子,问题所在便一目了然:
result = oneHalf.operator*(2);
result = 2.operator*(oneHalf);
对象oneHalf是一个包含 operator* 的类实例,所以编译器调用那个函数。然而整数2没有operator*成员函数。编译器同样要寻找可被如下调用的非成员operator*(也就是说,在 namespace 或全局范围内的operator*):
result = operator*(2, oneHalf);
但在本例中,没有非成员的接受int和Rational的operator*函数,所以搜索失败。再看那个成功的调用,它的第二个参数是整数2,而Rational::operator*却持有一个 Rational对象作为它的参数。这里发生了隐式类型转换。编译器知道你传递一个int而那个函数需要一个Rational,通过用你提供的int调用Rational的构造函数,它们能做出一个相配的Rational。换句话说,它们将那个调用或多或少看成如下这样:
const Rational temp(2); // 根据2建立一个临时Rational对象
result = oneHalf * temp; // 等同于oneHalf.operator*(temp);
当然,编译器这样做仅仅是因为提供了一个非explicit构造函数。
如果Rational的构造函数是explicit,那两句语句都将无法编译,但至少语句的行为保持一致。
这两个语句一个可以编译而另一个不行的原因在于,当参数列在参数列表中的时候,才有资格进行隐式类型转换。现在支持混合运算的方法或许很清楚了:让operator*作为非成员函数,因此就允许将隐式类型转换应用于所有参数:
class Rational {
... // 不包括operator*
};
const Rational operator*(const Rational& lhs, const Rational& rhs)
{
return Rational(lhs.numerator() * rhs.numerator(),
lhs.denominator()* rhs.denominator());
}
Rational oneFourth(1, 4);
Rational result;
result = oneFourth * 2; // fine
result = 2 * oneFourth; // it works!
另外,仅仅因为函数不应该作为成员并不自动意味着它应该作为友元。
关于operator*参考该文章条款21 必须返回对象时,别妄想返回其reference