[翻译] Effective C++, 3rd Edition, Item 24: 当希望将 type conversions(类型转换)应用于所有 parameters(参数)时,请声明为 non-member functions(非成员函数)

Item 24: 当希望将 type conversions(类型转换)应用于所有 parameters(参数)时,请声明为 non-member functions(非成员函数)

作者:Scott Meyers

译者:fatalerror99 (iTePub's Nirvana)

发布:http://blog.csdn.net/fatalerror99/

在本书的 Introduction(导言)中我谈到让 classes 支持 implicit type conversions(隐式类型转换)通常是一个不好的主意。当然,这条规则有例外,最普通的一种就是在创建数值类型时。例如,如果你设计一个代表 rational numbers(有理数)的 class,允许从 integers(整数)到 rationals(有理数)的 implicit conversions(隐式转换)看上去没什么不合理的。这的确不比 C++ 的从 intdouble 的 built-in conversion(内建转换)更不合理(而且比 C++ 的从 doubleint 的 built-in conversion(内建转换)要合理得多)。在这种情况下,你可以用这种方式开始你的 Rational class:

class Rational {
public:
  Rational(int numerator = 0,        // ctor is deliberately not explicit;
           int denominator = 1);     // allows implicit int-to-Rational
                                     // conversions

  int numerator() const;             // accessors for numerator and
  int denominator() const;           // denominator — see
Item 22

private:
  ...
};

你知道你应该支持像加法,乘法这样的算术运算,但是你还不能确定是通过 member functions(成员函数),non-member functions(非成员函数),还是 non-member functions that are friends(非成员的友元函数)来实现它们。你的直觉告诉你,当你摇摆不定的时候,你应该坚持面向对象的原则。你了解这一点,于是断定,因为有理数的乘法与 Rational class 有关,所以在 Rational class 的内实现有理数的 operator* 似乎是自然而然的。但是,与直觉不符的是,Item 23 指出将函数放在它们所关联的 class 的内部的主张有时候与面向对象的原则正好相反,但是让我们将它先放到一边,来研究一下让 operator* 成为 Rational 的一个 member function(成员函数)的想法究竟怎么样:

class Rational {
public:
 ...

 const Rational operator*(const Rational& rhs) const;
};

(如果你不能确定为什么这个函数声明成它现在这个样子——返回一个 const by-value 的结果,却取得一个 reference-to-const 作为它的参数——请参考 Items 32021。)

这个设计让你在有理数相乘时不费吹灰之力:

Rational oneEighth(1, 8);
Rational oneHalf(1, 2);

Rational result = oneHalf * oneEighth;            // fine

result = result * oneEighth;                      // fine

但是你并不感到满意。你还希望支持 mixed-mode(混合模式)的运算,以便让 Rationals 能够和其它类型(例如,ints)相乘。毕竟,很少有事情像两个数相乘那么正常,即使它们碰巧是不同类型的数。

然而,当你试着做混合模式的算术运算时,你发现它只有一半时间能够工作:

result = oneHalf * 2;                             // fine

result = 2 * oneHalf;                             // error!

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

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

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

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

object oneHalf 是一个包含 operator* 的 class 的实例,所以编译器调用那个函数。然而,整数 2 与 class 没什么关系,因此没有 operator* member function(成员函数)。编译器还要寻找能如下调用的 non-member(非成员)的 operator*s(也就是说,在 namespace(名字空间)或全局范围内的):

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

但是在本例中,没有 non-member(非成员)的取得一个 int 和一个 Rationaloperator*,所以搜索失败。

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

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

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

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

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

result = oneHalf * 2;                // error! (with explicit ctor);
                                     // can't convert 2 to Rational

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

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

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

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

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

其原因在于只有当 parameters(形参)列在 parameter list(形参列表)中的时候,它们才有资格进行 implicit type conversion(隐式类型转换)。而对应于 member function(成员函数)被调用的那个 object 的 implicit parameter(隐含参数)—— this 所指的那个——根本没有资格进行 implicit conversions(隐式转换)。这就是为什么第一个调用能编译而第二个不能。第一种情况包含的一个parameter(形参)被列在 parameter list(形参列表)中,而第二种则没有。

然而,你还是希望支持混合模式运算,现在做到这一点的方法或许很清楚了:让 operator* 成为一个 non-member function(非成员函数),这样就允许编译器将 implicit type conversions(隐式类型转换)应用于 all arguments(所有实参):

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 class 的 friend(友元)呢?

在这种情况下,答案是不,因为根据 Rational 的 public interface(公有接口) operator* 能够完整地实现。上面的代码展示了做这件事一种方法。这导出了一条重要的结论:与一个 member function(成员函数)相对的是一个 non-member function(非成员函数),而不是一个 friend function(友元函数)。太多的 C++ 程序员以为如果一个函数与一个 class 有关而又不应该是一个 member(成员)时(比方说,因为所有的 arguments(实参)都需要 type conversions(类型转换)),它应该是一个 friend(友元)。这个示例证明这样的推理是有缺陷的。无论何时,只有你能避免 friend functions(友元函数),你就应该避免它,因为,就像在现实生活中,朋友的麻烦通常多于他们的价值。当然,有时友谊是必需的,但是事实表明仅仅因为函数不应该是一个 member(成员)并不自动意味着它应该是一个 friend(友元)。

本 Item 包含真理,而且只有真理,但它还不是完整的真理。当你从 Object-Oriented C++ 穿过界线进入 Template C++(参见 Item 1)而且将 Rational 做成一个 class template(类模板)而不是一个 class 时,就有新的问题需要考虑,也有新的方法来解决它们,以及一些令人惊讶的设计关联性。这样的问题,解决方法和关联性是 Item 46 的主题。

Things to Remember

  • 如果你需要在一个函数的所有 parameters(参数)(包括 this 所指的那个)上使用 type conversions(类型转换),这个函数必须是一个 non-member(非成员)。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 26
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 26
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值