最近开始看《Effective C++》,为了方便以后回顾,特意做了笔记。若本人对书中的知识点理解有误的话,望请指正!!!
导读中提过令类支持隐式类型转换是不好的。但是这也是有例外的,最常见的例外就是在建立数值类型时,比如用一个类代表有理数,支持int的隐式转换为有理数是合理的。此外C++自己的内置类型也支持多种隐式转换,例如从int到double,那么我们也可以这样写这个有理数类:
class Rational{
public:
Rational(int numerator = 0, int denominator = 1);
int numerator() const;
int denominator() const;
...
};
既然上述的类时有理数,肯定支持诸如加法、乘法等算术运算,到底用成员函数还是非成员函数来实现呢,还是用非成员非友元函数实现?根据我们的直觉和面向对象的说法,有理数相乘应该在类内实现,即是成员函数。但条款23反直觉地主张,将函数放进相关类内有时会与面向对象守则发生矛盾,但我们先把它放在一旁,将 operator* 写成 Rational 成员函数:
class Rational{
public:
...
const Rational operator*(const Rational& rhs) const;
...
};
这个设计可以让两个 Rational 对象相乘:
Rational oneEighth(1,8);
Rational oneHalf(1,2);
Rational result = oneEighth * oneHalf;
result = result * oneEighth;
当你尝试混合运算时(即 Rational 对象和 int 对象相乘),发现只有一半行得通:
result = oneHalf * 2;
result = 2 * oneHalf;
乘法应该满足交换律,这样地结果不是我们想要的。
以对应的函数形式重写上述两个式子:
result = oneHalf.operator*(2);
result = 2.operator*(oneHalf);
问题所在一目了然,oneHalf 是一个内含 operator* 函数的类对象,所以编译器调用该函数。而整数 2 并没有相应的类,也就没有 operator* 成员函数。编译器也会尝试寻找可被以下这般调用的非成员 operator* (也就是在命名空间内或在 global 作用域内):
result = operator*(2, oneHalf);
如果找不到,那么就编译失败。
但我们看第一个成功的语句是不是也有一些疑问? 为什么我们定义的运算符的输入参数是 Rational 对象,但传进去整型 2 也可以编译? 其实这是隐式转换。编译器用我们传进去的 2 隐式地调用了 Rational 的构造函数,因此在编译器眼中是这样的:
const Rational tmp(2);
result = oneHalf * tmp;
当然,这是因为声明了 non-explicit 构造函数,编译器才会这样做,如果构造函数声明是 explicit ,以下语句全都编译错误:
result = oneHalf * 2;
result = 2 * oneHalf;
要记住只有在参数表里出现的参数才可以进行隐式转换。第一次调用,整型 2 在 * 的右侧,表明它在参数列表里,即 2 是形参 rhs ,而不再参数列表里的参数,即调用成员函数的对象,即this指向的,是不肯能被隐式转换。第二次调用整型 2 在 * 的左侧,表明是它调用了成员函数,所以编译不通过。
如果你一定要支持混合运算的话,那可以将 operator* 成为一个非成员函数,参与运算的对象都在参数列表内,即都可以进行隐式转换:
class Rational{...};
const Rational operator*(const Rational& lhs, const Rational& rhs)
{
return Rational(lhs.numerator() * rhs.numerator(),lhs.denominator() * rhs.denominator());
}
int main()
{
Rational oneFourth(1,4);
Rational result;
result = oneFourth * 2;
result = 2 * oneFourth;
return 0;
}
除了非成员函数,是否可以用友元函数来实现operator* 呢?就本例而言答案是否定的。因为我们从 Rational 的 public 接口就已经可以实现想要的功能了,上述代码已表明此种做法。从中得出一个结论:成员函数的反面是非成员函数,不是友元函数。太多C++程序员的人都会有的一个误区: 如果某个函数跟某个类相关,并且不能作为成员函数,那么它就是友元函数。这个例子证明这个想法是不必要的,而且可以避免使用友元函数就不要用。
条款25:考虑写出一个不抛异常的swap函数
|