C++箴言:声明为非成员函数时机

转载地址:http://www.enet.com.cn/article/2005/0808/A20050808442242_2.shtml

【简 介】
我谈到让一个类支持隐式类型转换通常是一个不好的主意。当然,这条规则有一些例外,最普通的一种就是在创建数值类型时。例如,如果你设计一个用来表现有理数的类,允许从整数到有理数的隐式转换看上去并非不合理。

 

   当你试图做混合模式的算术运算时,可是,你发现只有一半时间它能工作: 

 

result = oneHalf * 2; // fine 

result = 2 * oneHalf; // error! 

这是一个不好的征兆。乘法必须是可交换的,记得吗? 

当你重写最后两个例子为功能等价的另一种形式时,问题的来源就变得很明显了: 

 

result = oneHalf.operator*(2); // fine 

result = 2.operator*(oneHalf); // error! 

对象 oneHalf 是一个包含 operator* 的类的实例,所以编译器调用那个函数。然而,整数 2 与类没有关系,因而没有 operator* 成员函数。编译器同样要寻找能如下调用的非成员的 operator*s(也就是说,在 namespace 或全局范围内的 operator*s): 

 

result = operator*(2, oneHalf); // error! 

但是在本例中,没有非成员的持有一个 int 和一个 Rational 的 operator*,所以搜索失败。 

再看一眼那个成功的调用。你会发现它的第二个参数是整数 2,然而 Rational::operator* 却持有一个 Rational 对象作为它的参数。这里发生了什么呢?为什么 2 在一个位置能工作,在其它地方却不行呢? 

发生的是隐式类型转换。编译器知道你传递一个 int 而那个函数需要一个 Rational,但是它们也知道通过用你提供的 int 调用 Rational 的构造函数,它们能做出一个相配的 Rational,这就是它们的所作所为。换句话说,它们将那个调用或多或少看成如下这样: 

 

const Rational temp(2); // create a temporary 

// Rational object from 2 

 

result = oneHalf * temp; // same as oneHalf.operator*(temp); 

当然,编译器这样做仅仅是因为提供了一个非显性的构造函数。如果 Rational 的构造函数是显性的,这些语句都将无法编译: 

 

result = oneHalf * 2; // error! (with explicit ctor); 

// can’t convert 2 to Rational 

 

result = 2 * oneHalf; // same error, same problem 

支持混合模式操作失败了,但是至少两个语句的行为将步调一致。 

然而,你的目标是既保持一致性又要支持混合运算,也就是说,一个能使上面两个语句都可以编译的设计。让我们返回这两个语句看一看,为什么即使 Rational 的构造函数不是显式的,也是一个可以编译而另一个不行: 

 

result = oneHalf * 2; // fine (with non-explicit ctor) 

result = 2 * oneHalf; // error! (even with non-explicit ctor) 

其原因在于仅仅当参数列在参数列表中的时候,它们才有资格进行隐式类型转换。而对应于成员函数被调用的那个对象的隐含参数—— this 指针指向的那个——根本没有资格进行隐式转换。这就是为什么第一个调用能编译而第二个不能。第一种情况包括一个参数被列在参数列表中,而第二种情况没有。 

你还是希望支持混合运算,然而,现在做到这一点的方法或许很清楚了:让 operator* 作为非成员函数,因此就允许便一起将隐式类型转换应用于所有参数: 

 

class Rational { 

 

... // contains no operator* 

}; 

const Rational operator*(const Rational& lhs, // now a non-member 

const Rational& rhs) // function 



return Rational(lhs.numerator() * rhs.numerator(), 

lhs.denominator() * rhs.denominator()); 



Rational oneFourth(1, 4); 

Rational result; 

 

result = oneFourth * 2; // fine 

result = 2 * oneFourth; // hooray, it works! 

这样的确使故事有了一个圆满的结局,但是有一个吹毛求疵的毛病。operator* 应该不应该作为 Rational 类的友元呢? 

在这种情况下,答案是不,因为 operator* 能够根据 Rational 的 public 接口完全实现。上面的代码展示了做这件事的方法之一。这导出了一条重要的结论:与成员函数相对的是非成员函数,而不是友元函数。太多的程序员假设如果一个函数与一个类有关而又不应该作为成员时(例如,因为所有的参数都需要类型转换),它应该作为友元。这个示例证明这样的推理是有缺陷的。无论何时,只有你能避免友元函数,你就避免它,因为,就像在现实生活中,朋友的麻烦通常多于他们的价值。当然,有时友谊是正当的,但是事实表明仅仅因为函数不应该作为成员并不自动意味着它应该作为友元。 本 Item 包含真理,除了真理一无所有,但它还不是完整的真理。当你从 Object-Oriented C++ 穿过界线进入 Template C++而且将 Rational 做成一个类模板代替一个类,就有新的问题要考虑,也有新的方法来解决它们,以及一些令人惊讶的设计含义。 

Things to Remember 

·如果你需要在一个函数的所有参数(包括被 this 指针所指向的那个)上使用类型转换,这个函数必须是一个非成员。 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章