条款24::若所有参数皆需类型转换,请为此采用 non-member 函数
一般在class中很少设计支持隐式转换,但一般也有例外,比如对于一些数值类型的的类。
下面看下有理数的class
class Rational
{
public:
//没有加explicit,表明该构造函数可以隐式转换参数
Rational(const int numerator = 0, const int denominator = 1) : m_num(numerator), m_den(denominator) {}
int GetNum() const {return m_num;}
int GetDen() const {return m_den;}
private:
int m_num;
int m_den;
};
我们希望为有理数支持乘法运算,我们先使用member函数实现
const Rational operator *(const Rational &rhs) const
{
return Rational(this->m_num * rhs.m_num, this->m_den * rhs.m_den);
}
调用
Rational r1(1, 2);
Rational r2(3, 4);
Rational r3 = r1 * r2;
这样调用一切都是正常,但是对于数值,还希望支持混合运算,如int型
Rational r1(1, 2);
Rational r2(3, 4);
Rational r3 = r1 * 2; //ok
Rational r4 = 2 * r1; //error
第一种调用方式是:r1.operator*2, 由于Rational构造函数定义了默认参数,2在转为Rational类型时,编译器调用Rational构造函数,传入2为第一个参数,第二个参数为默认参数,最后转换的Rational为(2,2),然后与r1相乘。这一个过程就是隐式转换,这也有赖于将构造函数声明为no-explicit。
第二种调用方式是:编译器也尝试寻找整形对应的operator*函数,但没找到,只能报错处理。
只有当参数被列于参数列(parameterlist) 内,这个参数才是隐式类型转
换的合格参与者。地位相当于"被调用之成员函数所隶属的那个对象"一-即this 对
象一一的那个隐喻参数,绝不是隐式转换的合格参与者。这就是为什么上述第一次调
用可通过编译,第二次调用则否,因为第一次调用伴随一个放在参数列内的参数,第
二次调用则否。
那如何才能支持混合运算呢?这引出该条款的主题,no-member函数改派上用场了。
const Rational operator *(const Rational &lhs, const Rational &rhs)
{
return Rational(lhs.GetNum() * rhs.GetNum(), lhs.GetDen() * rhs.GetDen());
}
Rational r1(3, 5);
Rational r2(3, 4);
Rational r3 = r1 * 2; //ok (6, 5)
Rational r4 = 2 * r1; //0k (6, 5)
operator* 是否应该成为Rational class 的一个friend 函数呢?就本例而言不是的,因为operator* 可以完全藉由Rational 的public 接口完成任务。无论何时如果你可以避免friend 函数就该避免,因为就像真实世界一样,朋友带来的麻烦往往多过其价值。当然有时候friend 有其正当性,但这个事实依然存在:不能够只因函数不该成为member. 就自动让它成为friend。
记住
如果你需要为某个函数的所有参数(包括被this 指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是个non-member。