【问题标题】:Operator overloading : member function vs. non-member function?运算符重载:成员函数与非成员函数?
【发布时间】:2011-06-05 01:44:51
【问题描述】:

我读到声明为成员函数的重载运算符是不对称,因为它只能有一个参数,而自动传递的另一个参数是this 指针。所以没有标准来比较它们。另一方面,声明为friend 的重载运算符是对称的,因为我们传递了两个相同类型的参数,因此可以比较它们。

我的问题是,当我仍然可以将指针的左值与引用进行比较时,为什么首选朋友? (使用非对称版本的结果与对称版本相同) 为什么 STL 算法只使用对称版本?

【问题讨论】:

  • 您的问题实际上只是关于二元运算符。并非所有重载运算符都仅限于单个参数。 () 运算符可以采用任意数量的参数。另一方面,一元运算符不能有任何参数。
  • 这是C++ FAQ: Operator overloading中涵盖的众多主题之一

标签: c++ operator-overloading member-functions friend-function non-member-functions


【解决方案1】:

如果将运算符重载函数定义为成员函数,则编译器会将s1 + s2 等表达式转换为s1.operator+(s2)这意味着,运算符重载的成员函数在第一个操作数上被调用。这就是成员函数的工作原理!

但是如果第一个操作数不是一个类呢? 如果我们要重载第一个操作数不是类类型的运算符,而不是说double,则会出现一个主要问题。所以你不能这样写10.0 + s2。但是,您可以为 s1 + 10.0 之类的表达式编写运算符重载成员函数。

为了解决这个排序问题,我们将运算符重载函数定义为friend,如果它需要访问private成员。 仅在需要访问私有成员时才设置为friend 否则只需将其设置为非朋友非成员 功能以改进 封装!

class Sample
{
 public:
    Sample operator + (const Sample& op2); //works with s1 + s2
    Sample operator + (double op2); //works with s1 + 10.0

   //Make it `friend` only when it needs to access private members. 
   //Otherwise simply make it **non-friend non-member** function.
    friend Sample operator + (double op1, const Sample& op2); //works with 10.0 + s2
}

阅读这些:
A slight problem of ordering in operands
How Non-Member Functions Improve Encapsulation

【讨论】:

  • "只有当它需要访问私有成员时才使用friend..并且当您没有/厌倦编写访问器时,对吧?
  • @Abhi : 选择你的选择:改进封装与懒惰的写作习惯!
  • @matthias,并非所有运算符都是可交换的。简单的例子是a/b
  • 避免让非成员操作员需要friend 的一种常见方法是根据操作分配操作员(几乎可以肯定是公共成员)来实现它们。例如,您可以将T T::operator+=(const T &rhs) 定义为成员,然后将非成员T operator(T lhs, const T &rhs) 定义为return lhs += rhs;。非成员函数应该定义在与类相同的命名空间中。
  • @ricky:但是如果 lhs 是一个副本(就像我的评论中所说的那样),那么 lhs 更改的事实并不重要。
【解决方案2】:

friend 运算符重载和成员函数运算符重载之间不一定有区别,因为它是 全局 运算符重载和成员函数运算符重载之间的区别。

更喜欢 global 运算符重载的一个原因是,如果您想允许类类型出现在二元运算符的 右侧 一侧的表达式。例如:

Foo f = 100;
int x = 10;
cout << x + f;

这仅在存在全局运算符重载时才有效

Foo 运算符 + (int x, const Foo& f);

请注意,全局运算符重载不一定是friend 函数。仅当它需要访问 Foo 的私有成员时才需要这样做,但情况并非总是如此。

不管怎样,如果Foo只有一个成员函数操作符重载,比如:

class Foo
{
  ...
  Foo operator + (int x);
  ...
};

...那么我们将只能拥有 Foo 实例出现在加号运算符的 left 上的表达式。

【讨论】:

  • +1 用于区分成员函数和非成员函数,而不是成员函数和友元函数。我想今天我们会说“全局或命名空间范围”。