27

我在标题中看到我没有给自己写以下内容:

class MonitorObjectString: public MonitorObject {
   // some other declarations
   friend inline bool operator==(MonitorObjectString& lhs, MonitorObjectString& rhs) { return(lhs.fVal==rhs.fVal); }

我不明白为什么这个方法被声明为朋友。我认为如果函数在另一个地方定义并且需要访问类的内部成员是有意义的,但这里不是这种情况,因为它是内联的,甚至不需要访问成员。

你怎么看?“朋友”没用吗?

4

3 回答 3

52
friend inline bool operator==(MonitorObjectString& lhs, MonitorObjectString& rhs) { 
    return(lhs.fVal==rhs.fVal); 
}

有时称为friend definition,因为它是定义函数的友元声明。它将函数定义为围绕它出现的类的命名空间的非成员函数。实际上,那里的内联是多余的:如果它是友元定义,则它被隐式声明为内联。它的一些优点和缺点:

  • 它使操作符对正常查找不可见。您可以调用它的唯一方法是使用参数相关查找。这将使命名空间没有大量正常可见的运算符声明。请注意,这还将禁用使用隐式转换为 MonitorObjectString 调用它的能力(因为如果在查找要调用的候选者时两种参数类型不匹配,则依赖于参数的查找将找不到该函数)。
  • 名称的查找始于友元定义所在的类的范围。这意味着不需要写出长类型名称或其他名称。只需像在类的普通成员函数中那样引用它们即可。
  • 因为它是一个朋友,所以该函数可以看到MonitorObjectString. 但这既不好也不坏。这取决于实际情况。例如,如果有功能getFVal()使功能成为朋友是毫无意义的。那时也可以使用getFVal

我曾经喜欢这种操作符的朋友定义风格,因为它们可以直接访问类成员,并且出现在类定义中——所以我可以“一目了然”。然而,最近我得出的结论是,这并不总是一个好主意。如果您可以(并且应该)纯粹使用类的公共成员函数来实现运算符,则应该将其设为非友元(和非成员)运算符,定义在类的同一命名空间中。它确保如果您更改某些实现 - 但保持类的接口相同 - 运算符仍然可以工作并且您的级联更改更少,因为您知道它无法访问实现细节。

但是,我更喜欢这种风格而不是编写成员运算符,因为命名空间范围内的运算符函数具有与参数对称的附加特性:它们不会将左侧特殊对待,因为两侧只是普通参数而不是对象参数必然*this。如果左侧或右侧属于您的类的类型,则可以隐式转换另一侧 - 无论它是左侧还是右侧。对于同样在没有友元定义语法的情况下定义的函数(传统上,在命名空间范围内),您将具有选择性地包含使这些运算符可用或不可用的标头的功能。

于 2008-12-19T20:08:19.913 回答
7

从语法上讲...

仍然需要关键字来告诉编译器这个friend函数不是类的成员,编辑:而是一个可以看到类的私有成员的非成员函数。


但是,这可以像这样更干净地实现:

/* friend */ inline bool operator ==(const MonitorObjectString& rhs) const
{ return fVal == rhs.fVal; }

(当然,我假设fVal它是一种合适的类型,可以在不影响其常量性的情况下进行比较。)

于 2008-12-19T14:58:41.600 回答
6

它们并不相互排斥。“朋友”表示非成员函数可以访问类的私有成员。“内联”意味着没有函数调用调用,函数的主体在每个调用站点都被复制(在汇编中)。

于 2008-12-19T14:58:47.557 回答