运算符重载:成员函数 vs 非成员函数?

149

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

我的问题是,当我仍然可以将指针的lvalue与引用进行比较时,为什么要使用friend?(使用不对称版本会产生与对称版本相同的结果)为什么STL算法仅使用对称版本?


11
你的问题实际上只涉及二元运算符。并非所有重载运算符都仅限于单个参数。() 操作符可以接受任意数量的参数。而另一方面,一元运算符不能有任何参数。 - Charles Salvia
3
https://dev59.com/62855IYBdhLWcg3wUCWC#4421729 - Ben Voigt
4
这是C++常见问题解答:运算符重载中涵盖的众多主题之一。 - Ben Voigt
到了C++23,operator[]也可以作为一个带有多个参数的成员函数进行重载。 - Franky
到了C++23,operator[]也可以作为一个带有多个参数的成员函数进行重载。 - undefined
2个回答

184
如果你将你的运算符重载函数定义为成员函数,那么编译器会将像 s1 + s2 这样的表达式转换为 s1.operator+(s2)。这意味着运算符重载成员函数被调用时第一个操作数被传递给它。这就是成员函数的工作方式!
但如果第一个操作数不是类呢?如果我们想要重载一个以非类类型(比如double)作为第一个操作数的运算符,那就有一个大问题了。所以你不能这样写:10.0 + s2。不过,你可以对像 s1 + 10.0 这样的表达式编写运算符重载成员函数。
为了解决这个“顺序”问题,如果运算符重载函数需要访问私有成员,我们将其定义为友元函数。只有在需要访问私有成员时才将其设置为友元函数。否则,将其设置为非友元、非成员函数以提高封装性!
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
}

请阅读以下内容:
操作数顺序的轻微问题
非成员函数如何改进封装性


3
只有在需要访问私有成员的情况下,并且当你没有/厌倦编写访问器时,才将其设为“友元”。 - badmaash
4
选择你的做法:改进封装 vs 懒惰的写作习惯! - Nawaz
8
@matthias,并不是所有的运算符都满足交换律。一个简单的例子是a/b - edA-qa mort-ora-y
4
为了避免让非成员运算符需要使用 friend ,常见的方法是利用操作赋值运算符来实现它们(这些运算符几乎肯定是公开成员)。例如,你可以将 T T::operator+=(const T &rhs) 定义为成员函数,然后将非成员函数 T operator(T lhs, const T &rhs) 定义为 return lhs += rhs;。非成员函数应该在与类相同的命名空间中定义。 - Adrian McCarthy
2
@ricky:但如果lhs是一个副本(就像我的评论中一样),那么lhs的变化并不重要。 - Adrian McCarthy
显示剩余4条评论

23

这并不一定是朋友函数重载和成员函数重载之间的区别,而是全局运算符重载和成员函数运算符重载之间的区别。

选择使用全局运算符重载的一个原因是,如果您想允许表达式中类类型出现在二元运算符的右侧,则可以这样做。例如:

Foo f = 100;
int x = 10;
cout << x + f;
只有存在全局运算符重载,如下代码:

Foo operator + (int x, const Foo& f);

请注意,全局运算符重载不一定需要是friend函数。只有在需要访问Foo的私有成员变量时才需要这样,但并不总是这种情况。
如果Foo只有一个类成员函数的运算符重载,例如:
class Foo
{
  ...
  Foo operator + (int x);
  ...
};

如果我们这样做, 那么我们只能有这样的表达式,Foo实例只能出现在加号操作符的左侧


3
+1 表示区分成员函数和非成员函数,而不是成员函数和友元函数。我想今天我们会说“全局或命名空间范围”。 - Adrian McCarthy

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接