《Effective C++》学习笔记——条款24
***************************************转载请注明出处:http://blog.csdn.net/lttree********************************************
四、Designs and Declarations
Rule 24:Declare non-member functions when type conversions should apply to all parameters
规则 24:若所有参数皆需类型转换,请为此采用non-member函数
1.一个简单的开头
在本章开头简言中,说过:令classes支持隐式类型转换通常是一个糟糕的主意。
但是,肯定会有例外,最常见的就是在建立数值类型的时候。
假设你设计一个class用来表现有理数,允许整数“隐式转换”为有理数似乎颇为合理。
class Rational { public: Rational( int numerator = 0 , int denominator = 1 ); // 构造函数刻意不为explicit,允许 int-to-Rational 隐式转换 int numerator() const; // 分子的访问函数 int denominator() const; // 分母的访问函数 private: ... };
然后再让它支持算术运算,诸如加法、乘法等,
但不确定是否该由 member函数,non-member函数 或可能的话由 non-member friend函数来实现它们。
2.慢慢摸索,研究下 operator* 写成Rational成员函数的写法
class Rational { public: ... const Rational operator* ( const Rational& rhs ) const; };
这样声明后,下面这些方法就很正确的实现:
Rational oneEighth(1,8); Rational oneHalf(1,2); Rational result = oneHalf * oneEight; result = result * oneEight;
但是,如果想尝试一下混合式算术,比如整数和有理数的乘法:
result = oneHalf * 2; result = 2 * oneHalf;
第一个式子可以正确的执行,但是第二个却会报错,为什么?
当你重写上面两个式子,就会发现:
result = oneHalf.operator*(2); result = 2.operator*(oneHalf);
没错,oneHalf是一个内含 operator*函数的class对象,所以编译器调用该函数;
然而整数2并没有相应的class,也就没有operator*成员函数。
而且,此时,编译器也会尝试寻找下面这种调用的non-member operator*(也就是在命名空间内或在global作用域内):
result = operator*(2,oneHalf);
但是,本例并不存在这样一个接受 int 和 Rational作为参数的 non-member operator*,因此查找失败。
>再次看看先前成功的调用。
注意其第二个参数是整数2,但Rational::operator*需要的实参却是个Rational对象。
这里发生了什么事?
为什么2在这里可被接受,但在另一个调用中却不被接受?
because——这里发生了隐式转换。编译器知道你正在传递一个int,而函数需要的是Rational;但它也知道只要调用Rational构造函数并赋予你所提供的int,就可以变出一个适当的Rational来。
换句话说,此调用动作在编译器眼中有点像这样:
const Rantional temp(2); // 根据2建立一个暂时性的Rational对象 result = oneHalf * temp; // 等同于oneHalf.operator*(temp);
因为涉及 non-explicit 构造函数,编译器才会这样做。
如果Rational构造函数是explicit,以下语句没有一个会通过编译:
result = oneHalf * 2; // 在explicit构造函数的情况下,无法将2转换为一个Rational result = 2 * oneHalf; // 同样的错误
这就很难让Rational class 支持混合式算术运算了,不过至少上述两个句子的行为从此一致。
> 一致性搞定了,但是我们的目标不仅仅在此,还要支持混合式算术运算。
上面的例子,在non-explicit的情况下,仍然只有一个能通过编译,另一个不可以。
结论是——只有当参数被列于参数列内,这个参数才是隐式类型转换的合格参与者。
3.暂露头角——让operator*成为一个non-member函数
让operator*成为一个non-member函数,并允许编译器在每一个实参身上执行隐式类型转换:
class Rational { ... // 不包括operator*函数 }; const Rational operator* ( const Rational& lhs , const Rational& rhs ) // operator* 成为一个 non-member函数 { return Rational( lhs.numerator() * rhs.numerator() , lhs.denominator() * rhs.denominator() ); } Rational oneFourth(1,4); Rational result; result = oneFourth * 2; // 没问题 result = 2 * oneFourth; // 终于通过编译了!
终于搞定了,先别忙着开心,还有一个问题:operator* 是否应该成为Rational class的一个 friend 函数呢?
就本例而言,答案是否定的!
因为 operator* 可以完全藉由Rational 的public接口完成任务,上面代码就用了这种方法。
这就导出了一个重要的观察:member函数的反面是 non-member函数,并非是 friend函数。(这跟条款23要区分开)
而且,无论何时如果你可以避免 friend函数就该避免,因为朋友带来的麻烦往往多过其价值。
还有,不能够只因为函数不该称为member,就自动让它成为friend。
4.还有什么其他的?
本条款内含真理,但却不是全部的真理!
当你从 Object-Oriented C++ 跨进Template C++(见条款1)并让 Rational 成为一个 class template 而非 class,又有一些需要考虑的新争议、新解法、以及一些令人惊讶的设计牵连。
这些争议、解法和设计牵连形成了条款46。
5.请记住
★ 如果你需要为某个函数的所有参数(包括被 this指针所指的 那个隐喻参数 )进行类型转换,那么这个函数必须是个non-member。
***************************************转载请注明出处:http://blog.csdn.net/lttree********************************************
郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。