您的位置:首页 > 编程语言 > C语言/C++

Item 24:用非成员函数来支持所有元的类型转换 Effective C++笔记

2015-09-10 05:12 357 查看
Item 24: Declare non-member functions when type conversions should apply to all parameters.

虽然Item 15:资源管理类需要提供对原始资源的访问中提到,最好不要提供隐式的类型转化。
但这条规则也存在特例,比如当我们需要创建数字类型的类时。正如
double
int
能够自由地隐式转换一样,
我们的数字类型也希望能够做到这样方便的接口。 当然这一节讨论的问题不是是否应当提供隐式转换,而是如果运算符的所有“元”都需要隐式转换时,请重载该运算符为友元函数。

通过运算符重载来扩展用户定义类型时,运算符函数可以重载为成员函数,也可以作为友元函数。 但如果作为了成员函数,
*this
将被作为多元操作符的第一元,这意味着第一元不是重载函数的参数,它不会执行类型转换。
仍然拿有理数类作为例子,下面的
Rational
类中,将运算符
*
重载为成员函数:

class Rational{
public: 
    Rational(int n = 0, int d = 1);
    int numerator() const;
    int denominator() const;
    const Rational operator*(const Rational& rhs) const;
...


我们看下面的运算符调用能否成功:

Rational oneHalf(1, 2);

Rational result = oneHalf * oneHalf;   // OK
result = oneHalf * 2;                  // OK
result = 2 * oneHalf;                  // Error


第一个运算符的调用的成功是很显然的。我们看第二个调用:

当编译器遇到运算符
*
时,它会首先尝试调用:

result = oneHalf.operator*(2);


编译器发现该函数声明(它就是定义在
Rational
类中的方法)存在,
于是对参数
2
进行了隐式类型转换(
long
->
Rational
)。所以第二个调用相当于:

Rational tmp(2);
result = oneHalf.operator*(tmp);


Rational
的构造函数声明为
explicit
可以避免上述隐式转换,这样第二个调用也会失败。

对于第三个调用,编译器仍然首先尝试调用:

result = 2.operator*(oneHalf);


2
属于基本数据类型,并没有成员函数
operator*
。于是编译器再尝试调用非成员函数的运算符:

result = operator*(2, oneHalf);


再次失败,因为并不存在与
operator*(long,
 Rational)
类型兼容的函数声明,所以产生编译错误。 但如果我们提供这样一个非成员函数:

const Rational operator*(const Rational& lhs, const Rational& rhs);


这时候第一个参数也可以进行隐式转换。第三个调用(
result
 = 2 * oneHalf
)便会成功,该表达式相当于:

Rational tmp(2);
result = operator*(tmp, oneHalf);


只有当运算符的元出现在运算符函数的参数列表时,它才会被隐式类型转换。所以当我们需要运算符的所有“元”都可以被隐式转换时, 应当将运算符声明为非成员函数。 在JavaScript或者C#中,这个规则是不需要的,因为编译器/解释器在这里做了更多的工作。比如JavaScript中
2.toFixed(3)
会被解释为Number(2).toFixed(3)
,该表达式的值为
"2.000"`。

除非注明,本博客文章均为原创,转载请以链接形式标明本文地址: http://harttle.com/2015/08/22/effective-cpp-24.html
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: