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

令classes支持隐式类型转换通常是个糟糕的主意,当然这条规则有其例外,最常见的例外是建立数值类型时。
假设设计一个class用来表现有理数,允许整数“隐式转换”为有理数似乎颇为合理。

class Rational {
public:
    Rational(int numerator = 0, int denominator = 1); //构造函数刻意不为explicit,允许int-to-Rational隐式转换
    int numeator() const;
    int denominator() const;
private:
    ...
};

假设想支持算术运算诸如乘法等,但不确定是否该由member函数、non-member函数,或可能的话由non-member friend函数来实现它们。
先研究一下将operator*写成Rational成员函数的写法:

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

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

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

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

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

是的,oneHalf是一个内含operator*函数的class的对象,所以编译器应该调用函数。然而整数2并没有相应的class,也就没有operator*成员函数,本例不存在这样一个接受int和Rational作为参数的non-member operator*,因此编译器查找函数失败。
再次看看先前成功的那个调用,注意其第二参数是整数2,但Rational::operator*需要的实参却是个Rational对象。为什么呢?因为这里发生了所谓隐式类型转换(implicit type conversation)。编译器知道你正在传递一个int,而函数需要的Rational;但它也知道只要调用Rational 构造函数并赋予你所提供的int,就可以变一个适当的Rational来,于是它就这样做了。换句话说此一调用动作在编译器眼中有点像这样:

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

当然,只因涉及non-explicit构造函数,编译器才会这样做。如果Rational构造函数是explicit,前面的语句将没有一个能通过编译。
只有当参数被列于参数表内,这个参数才是隐式类型转换的合格参与者。this对象,绝不是隐式转换的合格参与者,这就是为什么上述第一次调用可通过编译,第二次调用则否,因为第一次调用伴随一个放在参数列内的参数,而第二次没有。
这就很难让Rational class支持混合式算术运算了,不过至少上述两个句子的行为从此一致。
然而你的目标不仅在一致性,也要支持混合式算术运算。
可行之道终于拨云见日:让operator*成为一个non-member函数,则允许编译器在每一个实参身上执行隐式转换:

class Rational {
    ...
};
const Rational operator*(const Rational& lhs, const Rational& rhs) {
    return Rational(lhs.numerator()*rhs.numerator(), lhs.denominator()*rhs.denominator());
}

这样终于通过编译了,不过还有一点必须操心:operator* 是否应该成为Rational class的friend函数呢?
就本例而言答案是否定的,因为operator* 可以完全藉由Rational的public接口完成任务,上面代码已表明此种做法。这导出一个重要的观察:member函数的反面是non-member函数,而不是friend函数。无论何时如果你可以避免friend函数就该避免。当然有时候friend有其正当性,但这个事实依然存在:不能只因函数不该成为member,就自动让他成为friend。

猜你喜欢

转载自blog.csdn.net/unirrrrr/article/details/81320084
今日推荐