一、看一个类型转换的错误案例
- 现在我们有一个类,来用表现出有理数,允许整数“隐式转换”为有理数
class Rational
{
public:
//构造函数未设置为explicit,因为我们希望一个int可以隐式转换为Rational
Rational(int numerator = 0, int denominator = 1);
int numerator()const;
int denominator()const;
const Rational operator*(const Rational& rhs)const;
private:
//...
};
- 现在我们我们可以使用下面的代码,来将两个有理数相乘,代码都是正确的:
int main()
{
Rational oneEighth(1, 8);
Rational oneHalf(1, 2);
Rational result = oneHalf*oneEighth;//正确
result = result*oneEighth; //也正确
return 0;
}
- 由于可以进行隐式的类型转换,因此我们还可以进行下面的有理数相乘,下面是正确的:
- 因为2可以隐式的转换为一个Rational类型的对象
- 然后传递给oneHalf对象的operator*()函数
int main()
{
Rational oneHalf(1, 2);
//2隐式转换为Rational类型
//然后下面代码等价于result=oneHalf.operator*(2)
Rational result = oneHalf * 2; //正确
return 0;
}
- 但是如下进行下面的处理就是错误的了:因为2不能作为一个Rational对象并调用operator*()函数,因此是错误的
int main()
{
Rational oneHalf(1, 2);
//代码相当于2.operator*(oneHalf)
Rational result = 2 * oneHalf; //错误
return 0;
}
二、类的隐式类型转换
- 在上面的操作中发生了隐式类型转换
-
explicit关键字:在构造函数前加上这个关键字,就可以阻止隐式类型转换
- 例如在上面的代码中,编译器知道你正传递一个int对象,但是operator*()需要的是Rational,因此它调用Rational构造函数并赋予你所提供的int,就可以构造出一个临时的Rational对象
int main()
{
Rational oneHalf(1, 2);
//2隐式转换为Rational类型
//然后下面代码等价于result=oneHalf.operator*(2)
Rational result = oneHalf * 2; //正确
return 0;
}
三、以非成员函数版本代替成员函数版本
-
为什么设计非成员函数版本:
- 从上面的“一”我们可以看到,如果operator*()为成员函数,在某些情况下即使存在隐式转换也不能成功执行
- 因此我们可以将成员函数版本改为非成员函数版本(见下面详细介绍)
- 例如下面将operator*()函数变为一个非成员函数。代码如下:
class Rational
{
public:
Rational(int numerator = 0, int denominator = 1);
int numerator()const;
int denominator()const;
private:
//...
};
const Rational operator*(const Rational& lhs,const Rational& rhs)
{
return Rational(lhs.numerator()*rhs.numerator,
lhs.denominator()*lhs.denominator());
}
int main()
{
Rational oneEighth(1, 8);
Rational oneHalf(1, 2);
Rational result;
result = oneHalf*oneEighth;
result = result*oneEighth;
result = oneHalf * 2;
result = 2 * oneHalf;
result = 2 * oneHalf*2;
result = oneHalf * 2 * oneHalf;
return 0;
}
一个疑惑:是否将非成员函数设置为friend?
-
对于这个例子来说是不需要的。因为上面我们的class已经定义了public operator*(),但是存在缺陷,于是我们就设计了一个非成员函数版本,但是该版本不需要称为friend
- 这也引入了一个规则:
- 如果一个函数不建议声明为member版本,那么就将该函数声明为non-member版本,而不是friend函数
- 能用non-member版本就尽量使用,不到万不得已不要使用friend函数
四、备注
- 这个条款并不适用于所有的情况,例如如果Rational是一个模板类,那么就需要考虑一些另外的情况。详情参阅条款46
五、总结
- 如果你需要为某个函数的所有参数(包括被this这孩子很所指的那个隐喻参数)进行类型转换,那么这个函数必须是个non-member