Effective C++设计声明之(若所有参数皆需类型转换,请为此采用non-member函数)
问题聚焦:
- member函数和 non-member 对于隐式类型转换的支持的区别?
通常来说,另classes支持隐式类型转换是个糟糕的注意,当然这条规则有例外是建立数值类型时。
假设有一个Rational 类
class Rational {
public:
Rational(int numerator = 0, int denominator = 1);
int numerator() const;
int denominator() const;
private:
};
使用member函数
想支持算数运算诸如加法、乘法等等,但不确定是否该由member函数、non-member函数,或可能的话由non-member friend函数来实现它们。先研究以下将operator*写成Rational成员函数的写法:
class Rational {
public:
Rational(int numerator = 0, int denominator = 1);
int numerator() const;
int denominator() const;
const Rational operator*(const Rational& rhs) const;
private:
};
这个设计能够将两个有理数以最轻松自在的方式相乘:
Rational oneEighth(1, 8);
Rational oneHalf(1, 2);
Rational result = oneHalf * oneEighth;
result = result * oneEighth;
如果要支持混合式运算,比如拿Rational和int相乘。然而当尝试混合式算术,发现只有一半行得通:
result = oneHalf * 2;//很好
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*,因此查找失败。
再次看看result = oneHalf.operator*(2),注意参数是整数2,但Rational::operator*需要的实参却是个Rational对象。**在这里可以接受参数整数2,是因为发生了隐式类型转换。**如果Rational构造函数是explicit,则以下语句都不能编译通过:
result = oneHalf * 2;//编译错误,在explicit构造函数的情况下,无法将2转换为一个Rational
result = 2 * oneHalf;//一样的错误,一样的问题
为什么即使Rational构造函数不是explicit,仍然只有一个可通过编译,另一个不可以:
结论:只有当参数被列于参数列内,这个参数才是隐式类型转换的合格参与者。
使用non-member函数
如果一定要支持混合式算术运算。可行之道:让operator*成为一个non-member函数,允许编译在每一个实参身上执行隐式类型转换:
class Rational {
//不再包括operator*
};
const Rational operator*(const Rational& lhs, const Rational& rhs)//现在成了一个non-member函数
{
return Rational(lhr.numerator() * rhs.numerator(), lhs.denominator() * rhs.denominator());
}
Rational oneFourth(1, 4);
Rational result;
result = oneFourth * 2;//OK
result = 2 * oneFourth;//OK,通过编译!
其他需要考虑:operator*是否应该成为Rational class的一个friend函数呢?
- 就这个例子而言答案是否定的,因为operator*可以完全由Rational的public接口完成任务,上面代码已表明此中做法。这导出一个重要的观察:member函数的反面是non-member函数,不是friend函数。无论何时如果可以避免friend函数就该避免,friend带来的麻烦往往多过其价值。当然有时候friend有其正当性,但这个事实依然存在:不能够只因函数不该成为member,就自动让它成为friend。
总结:
如果你需要为某个函数的所有参数(包括被this指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是个non-member。