《Effective C++》学习笔记(条款24:若所有参数皆需类型转换,请使用非成员函数)

最近开始看《Effective C++》,为了方便以后回顾,特意做了笔记。若本人对书中的知识点理解有误的话,望请指正!!!

导读中提过令类支持隐式类型转换是不好的。但是这也是有例外的,最常见的例外就是在建立数值类型时,比如用一个类代表有理数,支持int的隐式转换为有理数是合理的。此外C++自己的内置类型也支持多种隐式转换,例如从int到double,那么我们也可以这样写这个有理数类:

class Rational{
public:
    Rational(int numerator = 0, int denominator = 1);	//门不声明为explicit来允许从int到Rational的隐式转换
    int numerator() const;								//分子的访问函数
    int denominator() const;							//分母的访问函数
  	...
};

既然上述的类时有理数,肯定支持诸如加法、乘法等算术运算,到底用成员函数还是非成员函数来实现呢,还是用非成员非友元函数实现?根据我们的直觉和面向对象的说法,有理数相乘应该在类内实现,即是成员函数。但条款23反直觉地主张,将函数放进相关类内有时会与面向对象守则发生矛盾,但我们先把它放在一旁,将 operator* 写成 Rational 成员函数:

class Rational{
public:
    ...
    //(见条款3为什么要返回const,条款20为什么要使用引用传递,条款21为什么不返回引用)
    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);		//用整型 2 构造一个暂时性的Rational对象
result = oneHalf * tmp;	

当然,这是因为声明了 non-explicit 构造函数,编译器才会这样做,如果构造函数声明是 explicit ,以下语句全都编译错误:

result = oneHalf * 2; //编译错误,在explicit 构造函数情况下,无法将2转换为一个Rational对象
result = 2 * oneHalf; //一样的错误,一样的问题

要记住只有在参数表里出现的参数才可以进行隐式转换。第一次调用,整型 2 在 * 的右侧,表明它在参数列表里,即 2 是形参 rhs ,而不再参数列表里的参数,即调用成员函数的对象,即this指向的,是不肯能被隐式转换。第二次调用整型 2 在 * 的左侧,表明是它调用了成员函数,所以编译不通过。

如果你一定要支持混合运算的话,那可以将 operator* 成为一个非成员函数,参与运算的对象都在参数列表内,即都可以进行隐式转换

class Rational{...};//类里不声明operator*函数

//operator*作为非成员函数
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函数

上一篇:有理数类的设计


下一篇:Codeforces Round #692 (Div. 2) C. Peaceful Rooks(图论,并查集)