假如我们有以下的有理数类:
class Rational {public: Rational(int numerator = 0, int denominator = 1); int getnumerator() const; int getdenominator() const;private: int numerator; int denominator;};
在条款19中提到过设计class犹如设计type,也就是说我们的Rational类应该提供类似int类型的功能,因此我们必须添加一些运算符重载函数,标准的教科书函数是这样的,你将自己的重载函数声明为了一个类的成员函数,对一些设计来说这样是可以工作的,比如一下的代码:
Rational oneEight(1, 8);Rational oneHalf(1, 2);Rational result = oneHalf*oneEight;
作为一个有理数类我们希望他可以和一个整数相乘,于是我们就写出了以下的代码:
result = oneHalf * 2;result = 2 * oneHalf;
第一行的代码能够很好的工作,Oops!!!第二行的地方崩溃了,GCC会给出好几屏的错误提示,这点让人崩溃。下面就来简单分析一下为什么一个第一表达式能够正常运行,而第二个表达式不能正常运行。这次就是发生了所谓的隐士类型转换,编译器发现函数形参是一个Rational而实参是一个int后就给你做了隐士的类型转换,于是乎就有了类似以下的代码,他先用实参构造了一个Rational对象,然后再参与到函数中。
const Rational temp(2);result = oneHalf *temp
对于第二个表达式编译器就无能为力了,2是一个常量他压根就没有this指针也就无法调用类的成员函数。
C++的有些特性很是让人难受,编译器会在你不经意见来一个类型转换。
要实现完全和内置类型一样的乘法,我们有两种手段一种是将运算符重载为类的友元函数:
class Rational {public: Rational(int numerator = 0, int denominator = 1); int getnumerator() const; int getdenominator() const;friend const Rational operator*(const Rational& lhs, const Rational& rhs);private: int numerator; int denominator;};
而另外一种方式就是将重载运算符的函数声明为一个non-member函数:
class Rational {public: Rational(int numerator = 0, int denominator = 1); int getnumerator() const; int getdenominator() const;private: int numerator; int denominator;};const Rational operator*(const Rational& lhs, const Rational& rhs);
当我看到第二种方式的写法时,对一直将这些函数写成友元函数的我有点小震惊,之后想想这样的设计比起将其声明为友元函数更具有优点,最起码的你不用担心友元函数会破坏你的内部成员了。
这一条给了我一下的启示:
1. 这一条给人的第一感觉就是不要固守在别人给你的限定里,该这样做的时候就应该相信自己,有些东西可能违反一些常规,但那是最有效的。
2.在这一点也体现了提供对原始资源的访问是必须的。这样就可能减少使用friend的次数,避免friend 对数据的破坏是很有必要的。