Effective C++——条款24(第4章)
2015-09-06 12:33
621 查看
条款24: 若所有参数皆需类型转换,请为此采用non-member函数
Declare non-member functions when type conversions should apply to all paraments令 class 支持隐式类型转换通常是个糟糕的主意,当然这条规则有其例外,最常见的例外是在建立数值类型时.假设设计一个 class 用来表现有理数,允许整数"隐式转换"为有理数似乎颇为合理.的确,它并不比C++内置从 int 至 double 的转换来得不合理.假设这样开始Rational class:
class Rational { public: Rational(int numerator = 0, int denominator = 1); int numerator() const; int denominator() const; private: ... };如果想支持算数运算诸如加法,乘法等等,但不确定是否由member函数,non-member函数,如果可能的话由non-member friend 函数来实现它们.条款23曾反直觉地主张将函数放进相关 class 内有时会与面向对象守则发生矛盾.对于先研究一下将
operator*放在 class 内,写成Rational成员函数:
class Rational { public: ... const Rational operator* (const Rational& rhs) const; };这个设计使得能够将两个有理数以轻松的方式相乘:
Rational oneEighth(1, 8); Rational oneHalf(1, 2); Rational result = oneHalf * oneEighth; // ok result = result * oneEighth; // ok但是如果尝试混合式算法,会发现只有一半行得通:
result = oneHalf * 2; // ok result = 2 * oneHalf; // error当以对应的函数形式重写上述两个式子,问题所在便一目了然了:
result = oneHalf.operator*(2); // ok result = 2.operator*(oneHalf); // erroroneHalf是一个内含 operator* 的函数的 class 的对象,所以编译器会调用该函数.然而整数2并没有相应的 class,也就是没有 operator* 成员函数.编译器也会寻找可被以下这般调用的non-member operator*(也就是在命名空间内或在global作用域内):
result = operator*(2, oneHalf); // error但本例并不存在这样一个接受 int 和 Rational作为参数的non-member operator*,因此查找失败.
再看看先前成功的那个调用.注意其第二参数是整数2,但Rational::operator* 需要的实参却是个Rational对象.这里发生了什么事?为什么2在这里可被接受,在另一个调用中却不被接受?
因为这里发生了所谓隐式类型转换(implicit type conversion).编译器知道在传递一个 int,而函数需要的是Rational:但它也知道只要调用Rational构造函数并赋予所提供的 int,就可以变出一个适当的Rational来.于是它就那样做了.换句话说此调用动作在编译器眼中有点像这样:
const Rational temp(2); // 根据2建立一个暂时性的Rational对象 result = oneHalf * temp; // 等同于oneHalf.operator*(temp)当然,只因为涉及non-explicit 构造函数,编译器才会这样做.如果Rational构造函数是 explicit,一次啊语句没有一个可以通过编译:
result = oneHalf * 2; // error result = 2 * oneHalf; // error这就很难让Rational class 支持混合式算数运算了,不过至少上述两个句子的行为从此一致.
然而目标不仅在一致性,也要支持混合式算数运算,也就是希望有个设计能让上述语句通过编译.而之前的第一个式子是正确的,而第二个式子是错误的.结论是,只有当参数被列于参数列内,这个参数才是隐式类型转换的合格参与者.地位相当于"被调用的成员函数所隶属的那个对象"——即 this 对象——的那个隐喻参数,绝不是隐式转换的合格参与者.这就是为什么上述第一次调用可通过编译,第二次调用则否,因为第一次调用伴随一个放在参数列内的参数,第二次调用则否.
然而一定也会想支持混合式算数运算.可行之道终于拨云见日:让 operator* 成为一个non-member函数,以便允许编译器在每一个实参身上执行隐式类型转换:
class Rational { ... }; const Rational operator* (const Rational& lhs, const Rational& rhs) { return Rational(lhs.numberator() * rhs.numberator(), lhs.denominator() * rhs.denominator()); } Rational oneFourth(1, 4); Rational result; result = oneFourth * 2; // ok result = 2 * oneFourth; // ok这当然是个很好的结局,不过还有一点必须操心:operator* 是否应该成为Rational class 的一个 friend 函数呢?
就本例而言答案是否,因为 operator* 可以完全借由Rational的 public 接口完成任务.这导出一个重要的观察:member函数的反面是non-member函数,不是 friend 函数.不能够只因函数不应该成为member,就自动让它成为 friend.
注意:
如果需要为某个函数的所有参数(包括被 this 指针所指的那个隐喻参数)进行类型转换,那么这个函数必须是个non-member.
相关文章推荐
- C语言中send()函数和sendto()函数的使用方法
- C++中的多态性
- C++_运算符重载的注意事项
- C语言结构体中字符数组的问题
- C语言实现将字符串“askdaskaskdaskg”删除制定字符“ask”
- c++中的强制转换static_cast、dynamic_cast、reinterpret_cast的不同用法儿
- 最长连续序列
- 【more effective c++读书笔记】【第5章】技术(5)——Reference counting(引用计数)(1)
- 【more effective c++读书笔记】【第5章】技术(5)——Reference counting(引用计数)(1)
- C++primer阅读笔记-模板与泛型编程(模板实参推断)
- C语言实现回文判断(利用指针的方法)
- c++ char* char*&的区别
- C语言:文件操作
- C++三大继承构造函数的执行顺序详解
- C++要掌握的知识点
- 在VS2008环境下的C++异常处理
- 【C++】继承中的隐藏与覆盖
- C语言中等待socket连接和对socket定位的方法
- 从MVC架构到C++的多态实现
- C++入门经典 笔记 (第十八章)使用高级多态